司徒正美的 nodejs 学习笔记1——http
网上许多 nodejs 教程或书藉都是教你调用第三方模块来编写 nodejs 应用的,虽然这是非常便捷的,但是封装太厚,你基本一点东西还是没有学到。人家的模块,人家想怎么改就行,可以下一版本就改了接口,你的应用就完蛋了。比如说 google,他就爱干这种事情。因此我们还得老老实实学习底层 API 吧。
入门
本节首先教大家跑起一个页面吧。
我在以前就写一篇相关的, node.js 一个简单的页面输出,大家可以先预习一下。
一般来说,大家都是从这样一个例子入门
var http = require("http");
http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello node.js");
response.end();
}).listen(8888);
上面脚本写在一个 app.js上面,然后打开控制台,定位于它所在目录,输入node app,再打开某一浏览器,输入 http://localhost:8888/ 就看出效果。
分析上面脚本:
引入依赖,这是前端AMD规范未流行前, 非常神奇的东西。不过一个项目这么大,肯定要分成N个目录N个文件,各人负责一部分,减少合并冲突的风险
require("http")
吐出的 http
对象是一个原生对象,类似于前端的 alert, setTimeout,非常常用。
而这个 http
对象更是后端的中流砥柱,WEB 应用离不开它 http.createServer
方法要接收一个函数,俗称回调。以后你会看到越来越多回调,nodejs 的世界就是回调的世界,nodejs 的发展史就是跟回调的抗争史这个回调传给你两个重要对象,请求对象与响应对象。
response
要往前端输出东西,我们需要设置响应头(response.writeHead
),告诉浏览器接着下来要怎么处理我们的内容。因为有的东西可能会当成 script 脚本,有的当成图片,有的当成 CSS 文件,有的要当成附件下载……response.write
就是用来输出内容。输出结束了要调用 end
方法,这其实是方便 response
执行 end
回调。
http.createServer
其实是返回一个 Server
实例,它有一个listen
方法,它是用于监听某一端口。ok,这样就完了。我们深入一点吧(这里比较难,可以跳过,直接看下一个)。
深入分析
我们打开这里,查看http模块的源码,它大抵调用了
_http_incoming
_http_outgoing
_http_server
_http_client
这四个重要的内部模块,它们是我们在nodejs环境中访问不到。http还引用其他内部模块,但现在可以不理会它们。_http_incoming
,_http_outgoing
是提供两个输入输出流对象,流是以后我们重点学习的东西。在本模块中,除去哪些已经废弃的方法,主要是这三个方法:get
, request
, createServer
。
我将http源码删减一下,大家就明白什么回事了:
//------------------Server------------------
var server = require('_http_server');
exports.ServerResponse = server.ServerResponse;
var Server = exports.Server = server.Server;
exports.createServer = function(requestListener) {
return new Server(requestListener);
};
//------------------Client------------------
var client = require('_http_client');
var ClientRequest = exports.ClientRequest = client.ClientRequest;
exports.request = function(options, cb) {
return new ClientRequest(options, cb);
};
exports.get = function(options, cb) { //get是request方法的包装
var req = exports.request(options, cb);
req.end();
return req;
};
那么requestListener
是怎么传进去的呢?我们到_http_server
内部模块去,发现ServerResponse
只是OutgoingMessage
的子类,Server
实例是通过request
事件来绑定我们的createServer
回调,同时它也通过connection
事件绑定一个connectionListener
的方法,connectionListener
巨长,里面会emit request
事件。
if (!util.isUndefined(req.headers.expect) &&
(req.httpVersionMajor == 1 && req.httpVersionMinor == 1) &&
continueExpression.test(req.headers['expect'])) {
res._expect_continue = true;
if (EventEmitter.listenerCount(self, 'checkContinue') > 0) {
self.emit('checkContinue', req, res);
} else {
res.writeContinue();
self.emit('request', req, res);
}
} else {
self.emit('request', req, res);
}
res
是ServerResponse
的实例var res = new ServerResponse(req);
,req
是parserOnIncoming
方法传进来的,而parserOnIncoming
则是 parser.onIncoming
的一个方法. 追踪到_http_common
内部模块,发现以下几句:
parser.onIncoming(parser.incoming, info.shouldKeepAlive)//毫无疑问,parser.incoming就是我们的req对象
parser.incoming = new IncomingMessage(parser.socket);
好了,一切真相大白,req
是IncomingMessage
的实例, res
是ServerResponse
亦即OutgoingMessage
的实例。这里不得不吐槽,nodejs的源码太混乱了!
回归简单
我们又切换为简单模式。明白以上那段话,我们就知道我们为什么需要重点学习“http.ServerResponse”与“http.IncomingMessage”,如果大家看过express 的源码,就会发现其 request 模块与 response 模块就在这上面进行扩展的。
//https://github.com/strongloop/express/blob/master/lib/request.js
var req = exports = module.exports = {
__proto__: http.IncomingMessage.prototype
};
//https://github.com/strongloop/express/blob/master/lib/response.js
var res = module.exports = {
__proto__: http.ServerResponse.prototype
};
在好久之前,我们是通过fs模块的 fs.readFile
来读取服务器上的某个文件返回给前端:
var http = require("http");
var fs = require('fs');
exports.start = function(){
http.createServer(function(request, response) {
fs.readFile('./index.html', 'utf-8',function (err, data) {//读取内容
if (err) throw err;
response.writeHead(200, {"Content-Type": "text/html"});//注意这里
response.write(data);
response.end();
});
}).listen(8888);
console.log("server start...");
}
前面已经说过,现在已经是流的时代了,response是一个可写流(对应可读流),我们创建一个可读流就行了。
var http = require("http");
var fs = require("fs")
http.createServer(function (request, response) {
var readable = fs.createReadStream("./index.html")
response.writeHead(200, {"Content-Type": "text/html"});
readable.pipe(response);
}).listen(8888);
如果发生错误想跳转到404页面
var http = require("http");
var fs = require("fs")
http.createServer(function (request, response) {
var readable = fs.createReadStream("./index.html")
response.writeHead(200, {"Content-Type": "text/html"});
readable.pipe(response);
readable.on("error", function () {
var readable = fs.createReadStream("./404.html")
readable.pipe(response);
})
}).listen(8888);