文章目录
  1. 1. index.js
  2. 2. server.js
  3. 3. router.js
  4. 4. requestHandlers.js

本篇博客是《Node入门》的读后笔记。

读后感:
我只想说:这哥们,我太爱了。最后成型的代码,有4个文件:功能简单,但是里面从无到有、从不合理再改进到合理的感觉实在太踏实了。不杂七杂八地引入其他不相干的概念,让整篇博客简洁、有序而又完整。总之,读着读着崇拜之心肆起,导致很有一种想娶Ta的冲动。

读书笔记:
实现功能:
1.请求服务器,得到一个可上传图片的表单
2.上传图片,提交表单
3.在页面上显示图片

一共4个文件,分别是:
1.index.js:主文件,server、router、handle 的粘合剂 【一件事:主人】
2.server.js:服务器,解析url 并把 handle request response 传给 router 【两件事:解析路径、传参】
3.router.js:路由,只路由,把 request response 传给相应的 handle【两件事:认识的传参,不认识的就404】
4.requestHandlers.js:请求处理器,好吧,这货才是真正底层的干实事的人儿 【一件事:干活】

从上面可以看出:各种参数接龙呀
1.请求-响应的关联数组:从 index -> server -> router
2.路由 router:从 index -> server
3.处理器 handle:从 index -> server -> router
4.request 和 response:从 server -> router -> handle

想知道为什么要写这么复杂扭曲麻烦的传参过程吗?怀着你满满的好奇心去读读原博吧,看看我老公怎么滴娓娓道来。此处主要写笔记的,就不大篇幅复制粘贴了。下面分别看看这4个文件,并浅聊下自己的疑惑、涨的姿势、认可、崇拜、以及疑惑。

index.js

1
2
3
4
5
6
7
8
9
10
11
12
var server = require('./server');
var router = require('./router');
var requestHandlers = require('./requestHandlers');
//干净整洁:url到真正的处理
var handle = {};
handle['/'] = requestHandlers.start;
handle['/start'] = requestHandlers.start;
handle['/upload'] = requestHandlers.upload;
handle['/show'] = requestHandlers.show;
server.start(router.route, handle); //将路由函数注入到服务器中
  1. 对象 handle:把 url 对应到真正的请求处理函数
    如此书写干净整洁,当成参数被传递到 server 端更灵活。而不是把对应关系硬编码到 server 端,还记得在学校实验室时写的一长串 switch-case 吗?还记得当时加一个新页面,就要从页面、controler、后端类走一遍整个流程吗?还记得当时熟悉的 ctrl+c、ctrl+v、再修改文本时流畅的体力劳动吗?
  2. 把 handle 和 router 一起传到 server
    这种结构让 server、router 更通用了。倘若要换个后台处理器,则只需要重新定义 handle 对象即可-并当成参数传过去即可。而且结构和层级都比较清晰,各部分耦合也低,方便维护。

其他:

  1. HTTP 服务器要提供 web 页面:路由-不同url不同的响应,把请求传到对应的‘请求处理程序’那里,请求处理程序-处理请求
  2. ?? 在主js的文件里,暴露了router和handle,总觉得怪怪的。对于我这种纯浏览器端js的纯FEer,还真是没拐过来弯儿来…【那个,醒醒,此处是“后端JavaScript”的地盘。另外,此时你console.log()下,那就直接打印到神秘的黑屏上了哦~~日志】
  3. ?? 感觉把关联数组 handle 写在 router 里也可以啊….【但是,貌似 router 就不通用了-和业务本身挂钩】嗯,所以此处是把 server 和 router都“提”出来了,只有 handle+requestHandlers 和具体业务相关。这么一想,确实八错。

server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
var http = require('http'); //http 服务器的核心嘛
var url = require('url'); //url 解析路径
function start(route, handle){
http.createServer(function(request, response){
var pathname = url.parse(request.url).pathname; //解析路径
console.log('Request for ' + pathname + ' received.');
route(handle, pathname, response, request); //传参
}).listen(8888); //监听端口8888
console.log('Server has started\nhttp://localhost:8888/');
}
exports.start = start;
  1. 创建服务器时,为什么要搞个回调函数 function(request, response){} 呢?
    因为 nodeJS 是事件驱动的,这是它原生的工作方式。而,用它来写网络应用是有意义的,因为 HTTP 请求到来的时候就是异步的,而我们的服务器是跑在一个单进程中的。一句话就是:nodeJS/JavaScript 事件驱动,当事件来了,就去执行传过来的回调。#事件驱动的异步服务器JS和它的回调#
    Note:php是任何时候有请求来时,服务器(通常是 apache)就为请求新建一个进程,且从头到尾执行相应的php脚本
  2. 没有直接在 server 端 return response,而是把 response 通过 route 传递给 handle
    原因:如果在 server 里静等 handle 返回结果,然后统一返回 response,那就会导致 当未来要处理比较耗时的操作时,我们的服务器就“挂”了,即服务器被阻塞了。
    Note:因为 nodeJS 是单线程的,它通过事件轮询(event loop)来实现并行操作,所以我们要充分利用这一点-尽可能避免阻塞操作,多使用非阻塞操作。
  3. 把 request 通过 route 传递给 handle
    原因:get啊、post啊、数据啊等,一切都由请求处理程序处理更合适。形式和数据辣么多样,是吧。

其他:

  1. 当访问网页时,服务器可能会输出两次”Request received”,那是因为大部分服务器会在你访问 http://localhost:8888/ 时尝试读取 http://localhost:8888/favicon.ico
  2. 它应当属于路由or服务器or一个模块自身的功能,确实值得探讨,这里暂定HTTP服务器的功能【特别喜欢人家的坦诚,把不确定的 不知道的都真诚的明明白白地说出来】
  3. 可以用 querystring 模块来解析 POST 请求体中的参数
  4. 其实说白了,处理文件上传“就是”处理POST数据
  5. 如何重启服务? ctrl+c 杀死进程的监听端口

router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
function route(handle, pathname, response, request){
console.log('About to route a request for ' + pathname);
if(typeof handle[pathname] === 'function'){
return handle[pathname](response, request);
}else{
console.log("No request handler found for " + pathname);
response.writeHead(404, {"Content-type":"text/plain"});
response.write('404 Not Found');
response.end();
}
}
exports.route = route;
  1. 路由:并不是真正有所行动的模块
    在现在的实现下,路由过程会在路由模块中“结束”,并且路由模块并不是真正针对请求“采取行动”的模块,否则当我们的应用程序变得更为复杂时,将无法很好地扩展
  2. 函数式编程
    其实是一种编程哲学,即直接传“动作”,而不是传“名词”。举例:我们传过去一个东西,别人可以这么用:“嗨那个叫路由的东西,能帮我把这个路由一下吗?”其实你不需要东西,你需要的是一个动作….即你不需要名词,而是需要动词。可以看看 《名词王国中的死刑》 来更好的理解函数式编程

requestHandlers.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
var querystring = require('querystring');
var fs = require('fs'); //将文件读取到服务器中
var formidable = require('formidable'); //外部模块:对解析上传的文件做了很好的抽象
/*返回页面:有一个表单,文件上传按钮+提交按钮*/
function start(response, request){
console.log('Request handler "start()" was called.');
var body = '<!DOCTYPE html>'+
'<html lang="en">'+
'<head>'+
'<meta charset="UTF-8">'+
'<title>Node</title>'+
'</head>'+
'<body>'+
'<form action="/upload" method="post" enctype="multipart/form-data">'+
'<input type="file" name="upload" />'+
'<input type="submit" value="Submit Text" />'+
'</form>'+
'</body>'+
'</html>';
response.writeHead(200, {"Content-Type": "text/html"});
response.write(body);
response.end();
}
/*上传文件到服务器目录下*/
function upload(response, request){
console.log('Request handler "upload()" was called.');
var form = new formidable.IncomingForm(); //创建一个新的IncomingForm,它是对提交表单的抽象表示
console.log('about to parse');
//用form来解析request对象,获取表单中需要的数据字段
form.parse(request, function(err, fields, files){
console.log('parsing to done');
fs.renameSync(files.upload.path, "tmp/test3.png"); //临时存在tmp目录下,供show()去show
response.writeHead(200, {"Content-Type": "text/html"});
response.write('received image:<br/>');
response.write('<img src="/show" />');
response.end();
});
}
/*展示服务器目录下的一张图片*/
function show(response, request){
console.log('Request handler "show" was called.');
fs.readFile('tmp/test3.png', 'binary', function(err, file){
if(err){
response.writeHead(500, {"Content-Type": "text/plain"});
response.write(err+'\n');
response.end();
}else{
response.writeHead(200, {"Content-Type": "image/png"});
response.write(file, 'binary');
response.end();
}
});
}
exports.start = start;
exports.upload = upload;
exports.show = show;
  1. 选择:是将 requestHandlers 模块硬编码到路由里来使用,还是再添加一点依赖注入?
    虽然和其他模式一样,依赖注入不应该仅仅为使用而使用,但在现在这个情况下,使用依赖注入可以让路由和请求处理程序之间的耦合更加松散,也因此能让路由的重用性更高。这里,选择了“依赖注入”。

其他:

  1. 当是 POST 请求时,
    (1)Node.js 会将 post 数据拆分成很多小的数据块 -> 触发特定事件(将小块数据传递给回调函数)
    eg. data 事件,新的小数据块到达了;end 事件,所有的数据都已接收完毕
    (2)我们要做的是:告诉Node.js当这些事件触发时,都回调哪些函数。
    怎么告诉? 在 request 对象上注册监听器,request 对象是每次接收到 HTTP 请求时,都会把该对象传递给 onRequest 回调函数。
    request.data(); request.end();

很爱博主的几个小点:

  1. 一条线很清晰:该扩展的扩展,该省略的省略;该详细的详细,该讲解的讲解,该不懂的人家也不装懂(其实更多的是站在读者的角度去进行个思维转换-让读者有代入感)….特别喜爱这样的人。读某些文章,简直就是一种享受。
  2. “我不想去解释 noun1 和 noun2 的具体含义,我们直接来看,当程序中加入了 noun1 的操作后会发生什么。”
    这就是程序员自己的魅力啊,不需要单纯的讲解概念本身,而是实战理论相结合。帅气。
  3. 最直截了当的实现方式事实上并不是非常靠谱:看似有效,实则未必如此。我们先就这样去实现,然后再来看为什么这不是一种很好的实现方式。
    看看看,循循善诱的方式啊,多温暖,多温柔,多有耐心。
文章目录
  1. 1. index.js
  2. 2. server.js
  3. 3. router.js
  4. 4. requestHandlers.js