網上許多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);