一,開篇分析
首先“Http”這個概念大家應該比較熟悉了,它不是基於特定語言的,是一個通用的應用層協議,不同語言有不同的實現細節,但是萬變不離其宗,思想是相同的,
NodeJS作為一個宿主運行環境,以JavaScript為宿主語言,它也有自己實現的一套標准,這篇文章我們就一起來學習一下 “Http模塊” 。但是作為前提來說,
希望大家可以先閱讀一下官網提供的api,有一個前置了解,這樣就方便多了,以下是Http部分的api概覽:
代碼如下:
HTTP
http.STATUS_CODES
http.createServer([requestListener])
http.createClient([port], [host])
Class: http.Server
事件 : 'request'
事件: 'connection'
事件: 'close'
Event: 'checkContinue'
事件: 'connect'
Event: 'upgrade'
Event: 'clientError'
server.listen(port, [hostname], [backlog], [callback])
server.listen(path, [callback])
server.listen(handle, [callback])
server.close([callback])
server.maxHeadersCount
server.setTimeout(msecs, callback)
server.timeout
Class: http.ServerResponse
事件: 'close'
response.writeContinue()
response.writeHead(statusCode, [reasonPhrase], [headers])
response.setTimeout(msecs, callback)
response.statusCode
response.setHeader(name, value)
response.headersSent
response.sendDate
response.getHeader(name)
response.removeHeader(name)
response.write(chunk, [encoding])
response.addTrailers(headers)
response.end([data], [encoding])
http.request(options, callback)
http.get(options, callback)
Class: http.Agent
new Agent([options])
agent.maxSockets
agent.maxFreeSockets
agent.sockets
agent.freeSockets
agent.requests
agent.destroy()
agent.getName(options)
http.globalAgent
Class: http.ClientRequest
Event 'response'
Event: 'socket'
事件: 'connect'
Event: 'upgrade'
Event: 'continue'
request.write(chunk, [encoding])
request.end([data], [encoding])
request.abort()
request.setTimeout(timeout, [callback])
request.setNoDelay([noDelay])
request.setSocketKeepAlive([enable], [initialDelay])
http.IncomingMessage
事件: 'close'
message.httpVersion
message.headers
message.rawHeaders
message.trailers
message.rawTrailers
message.setTimeout(msecs, callback)
message.method
message.url
message.statusCode
message.socket
讓我們先從一個簡單例子開始,創建一個叫server.js的文件,並寫入以下代碼:
代碼如下:
var http = require('http') ;
var server = http.createServer(function(req,res){
res.writeHeader(200,{
'Content-Type' : 'text/plain;charset=utf-8' // 添加charset=utf-8
}) ;
res.end("Hello,大熊!") ;
}) ;
server.listen(8888) ;
console.log("http server running on port 8888 ...") ;
(node server.js)以下是運行結果:
二,細節分析實例
具體看一下這個小例子:
(1行):通過"require"引入NodeJS自帶的"http"模塊,並且把它賦值給http變量。
(2行):調用http模塊提供的函數:"createServer" 。這個函數會返回一個新的web服務器對象。
參數 "requestListener" 是一個函數,它將會自動加入到 "request" 事件的監聽隊列。
當一個request到來時,Event-Loop會將這個Listener回調函數放入執行隊列, node中所有的代碼都是一個一個從執行隊列中拿出來執行的。
這些執行都是在工作線程上(Event Loop本身可以認為在一個獨立的線程中,我們一般不提這個線程,而將node稱呼為一個單線程的執行環境),
所有的回調都是在一個工作線程上運行。
我們在再來看一下"requestListener"這個回調函數,它提供了兩個參數(request,response),
每次收到一個請求時觸發。注意每個連接又可能有多個請求(在keep-alive的連接中)。
"request"是http.IncomingMessage的一個實例。"response"是http.ServerResponse的一個實例。
一個http request對象是可讀流,而http response對象則是可寫流。
一個"IncomingMessage"對象是由http.Server或http.ClientRequest創建的,
並作為第一參數分別傳遞給"request"和"response"事件。
它也可以被用來訪問應答的狀態,頭文件和數據。
它實現了 "Stream" 接口以及以下額外的事件,方法和屬性。(具體參考api)。
(3行):“writeHeader”,使用 "response.writeHead()" 函數發送一個Http狀態200和Http頭的內容類型(content-type)。
向請求回復響應頭。"statusCode"是一個三位是的HTTP狀態碼,例如 404 。最後一個參數,"headers",是響應頭的內容。
舉個栗子:
代碼如下:
var body = 'hello world' ;
response.writeHead(200, {
'Content-Length': body.length,
'Content-Type': 'text/plain'
}) ;
注意:Content-Length 是以字節(byte)計算,而不是以字符(character)計算。
之前的例子原因是字符串 “Hello World !” 只包含了單字節的字符。
如果body包含了多字節編碼的字符,就應當使用Buffer.byteLength()來確定在多字節字符編碼情況下字符串的字節數。
需要進一步說明的是Node不檢查Content-Lenth屬性和已傳輸的body長度是否吻合。
statusCode是一個三位是的HTTP狀態碼, 例如:"404" 。這裡要說的是 "http.STATUS_CODES" ,全部標准"Http"響應狀態碼的集合和簡短描述都在裡面。
如下是源碼參考:
代碼如下:
var STATUS_CODES = exports.STATUS_CODES = {
100 : 'Continue',
101 : 'Switching Protocols',
102 : 'Processing', // RFC 2518, obsoleted by RFC 4918
200 : 'OK',
201 : 'Created',
202 : 'Accepted',
203 : 'Non-Authoritative Information',
204 : 'No Content',
205 : 'Reset Content',
206 : 'Partial Content',
207 : 'Multi-Status', // RFC 4918
300 : 'Multiple Choices',
301 : 'Moved Permanently',
302 : 'Moved Temporarily',
303 : 'See Other',
304 : 'Not Modified',
305 : 'Use Proxy',
307 : 'Temporary Redirect',
400 : 'Bad Request',
401 : 'Unauthorized',
402 : 'Payment Required',
403 : 'Forbidden',
404 : 'Not Found',
405 : 'Method Not Allowed',
406 : 'Not Acceptable',
407 : 'Proxy Authentication Required',
408 : 'Request Time-out',
409 : 'Conflict',
410 : 'Gone',
411 : 'Length Required',
412 : 'Precondition Failed',
413 : 'Request Entity Too Large',
414 : 'Request-URI Too Large',
415 : 'Unsupported Media Type',
416 : 'Requested Range Not Satisfiable',
417 : 'Expectation Failed',
418 : 'I\'m a teapot', // RFC 2324
422 : 'Unprocessable Entity', // RFC 4918
423 : 'Locked', // RFC 4918
424 : 'Failed Dependency', // RFC 4918
425 : 'Unordered Collection', // RFC 4918
426 : 'Upgrade Required', // RFC 2817
500 : 'Internal Server Error',
501 : 'Not Implemented',
502 : 'Bad Gateway',
503 : 'Service Unavailable',
504 : 'Gateway Time-out',
505 : 'HTTP Version not supported',
506 : 'Variant Also Negotiates', // RFC 2295
507 : 'Insufficient Storage', // RFC 4918
509 : 'Bandwidth Limit Exceeded',
510 : 'Not Extended' // RFC 2774
};
節選自,Nodejs源碼 ”http.js“ 143行開始。
其實從客戶端應答結果也不難看出:
(6行):”response.end“------當所有的響應報頭和報文被發送完成時這個方法將信號發送給服務器。服務器會認為這個消息完成了。
每次響應完成之後必須調用該方法。如果指定了參數 “data” ,就相當於先調用 “response.write(data, encoding) ” 之後再調用 “response.end()” 。
(8行):”server.listen(8888)“ ------ 服務器用指定的句柄接受連接,綁定在特定的端口。
以上就是一個比較詳細的分析過程,希望有助於加深理解,代碼雖然不多,但是重在理解一些細節機制,以便日後高效的開發NodeJS應用。
三,實例
除了可以使用"request"對象訪問請求頭數據外,還能把"request"對象當作一個只讀數據流來訪問請求體數據。
這是一個"POST"請求的例子:
代碼如下:
http.createServer(function (request, response) {
var body = [];
console.log(request.method) ;
console.log(request.headers) ;
request.on('data', function (chunk) {
body.push(chunk);
}) ;
request.on('end', function () {
body = Buffer.concat(body) ;
console.log(body.toString()) ;
});
}).listen(8888) ;
下是一個完整的“Http”請求數據內容。
代碼如下:
POST / HTTP/1.1
User-Agent: curl/7.26.0
Host: localhost
Accept: */*
Content-Length: 11
Content-Type: application/x-www-form-urlencoded
Hello World
四,總結一下
(1),理解 "Http" 概念。
(2),熟練使用 "Http" 相關的api。
(3),注意細節的把控,比如:“POST,GET” 之間的處理細節。
(4),"requestListener"的理解。
(5),強調一個概念:一個http request對象是可讀流,而http response對象則是可寫流 。