Node.js 之 HTTP實現詳細分析(下)

分針網每日分享:Node.js 之 HTTP實現詳細分析(下)node

 

Node.js 之 HTTP實現詳細分析(上)講了node代碼思路分析,還講了一下事件。這篇繼續讓咱們瞭解一下node。
 
1. Expect頭
 
若是客戶端在發送POST請求以前,因爲傳輸的數據量比較大,指望向服務器確認請求是否能被處理;這種狀況下,能夠先發送一個包含頭Expect:100-continue的http請求。若是服務器能處理此請求,則返回響應狀態碼100(Continue);不然,返回417(Expectation Failed)。默認狀況下,Node.js會自動響應狀態碼100;同時,http.Server會觸發事件checkContinue和checkExpectation來方便咱們作特殊處理。具體規則是:當服務器收到頭字段Expect時:若是其值爲100-continue,會觸發checkContinue事件,默認行爲是返回100;若是值爲其它,會觸發checkExpectation事件,默認行爲是返回417。
 
例如,咱們經過curl發送HTTP請求:
 
curl -vs --header "Expect:100-continue" http://localhost:3333
 
交互過程以下
 
> GET / HTTP/1.1
> Host: localhost:3333
> User-Agent: curl/7.49.1
> Accept: */*
> Expect:100-continue
>
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Date: Mon, 03 Apr 2017 14:15:47 GMT
< Connection: keep-alive
< Content-Length: 11
<
 
咱們接收到2個響應,分別是狀態碼100和200。前一個是Node.js的默認行爲,後一個是應用程序代碼行爲。
 
2. HTTP代理
 
在實際開發時,用到http代理的機會仍是挺多的,好比,測試說線上出bug了,觸屏版頁面顯示有問題;咱們通常第一時間會去看api返回是否正常,這個時候在手機上設置好代理就能輕鬆捕獲HTTP請求了。老牌的代理工具備fiddler,charles。其實,nodejs下也有,例如node-http-proxy,anyproxy。基本思路是監聽request事件,當客戶端與代理創建HTTP鏈接以後,代理會向真正請求的服務器發起鏈接,而後把兩個套接字的流綁在一塊兒。咱們能夠實現一個簡單的代理服務器:
 
var http = require('http');
var url = require('url');
 
http .createServer((req, res) => {
// request回調函數
console .log(`proxy request: ${req.url}`);
var urlObj = url.parse(req.url);
var options = {
hostname : urlObj.hostname,
port : urlObj.port || 80,
path : urlObj.path,
method : req.method,
headers : req.headers
};
// 向目標服務器發起請求
var proxyRequest = http.request(options, (proxyResponse) => {
// 把目標服務器的響應返回給客戶端
res .writeHead(proxyResponse.statusCode, proxyResponse.headers);
proxyResponse .pipe(res);
}).on('error', () => {
res .end();
});
// 把客戶端請求數據轉給中間人請求
req .pipe(proxyRequest);
}).listen(8089, '0.0.0.0');
 
驗證下是否真的起做用,curl經過代理服務器訪問咱們的「hello world」版Node.js服務器:
 
curl -x http://192.168.132.136:8089 http://localhost:3333/
 
優化策略
 
Node.js在實現HTTP服務器時,除了利用高性能的http-parser,自身也作了些性能優化。
 
1. http_parser對象緩存池
 
http-parser對象處理完一個請求以後不會被當即釋放,而是被放入緩存池(/lib/internal/freelist),最多緩存1000個http-parser對象。
 
2. 預設HTTP頭總數
 
HTTP協議規範並無限定能夠傳輸的HTTP頭總數上限,http-parser爲了不動態分配內存,設定上限默認值是32。其餘web服務器實現也有相似設置;例如,apache能處理的HTTP請求頭默認上限(LimitRequestFields)是100。若是請求消息中頭字段真超過了32個,Node.js也能處理,它會把已經解析的頭字段經過事件kOnHeaders保存到JavaScript這邊而後繼續解析。 若是頭字段不超過32個,http-parser會直接處理完並觸發on_headers_complete一次性傳遞全部頭字段;因此咱們在利用Node.js做爲web服務器時,應儘可能把頭字段控制在32個以內。
 
3. 過載保護
 
理論上,Node.js容許的同時鏈接數只與進程能夠打開的文件描述符上限有關。可是隨着鏈接數愈來愈多,佔用的系統資源也愈來愈多,頗有可能連正常的服務都沒法保證,甚至可能拖垮整個系統。這時,咱們能夠設置http.Server的maxConnections,若是當前併發量大於服務器的處理能力,則服務器會自動關閉鏈接。另外,也能夠設置socket的超時時間爲可接受的最長響應時間。
 
性能實測
 
爲了簡單分析下Node.js引入的開銷,如今基於libuv和http_parser編寫一個純C的HTTP服務器。基本思路是,在默認事件循環隊列上監聽指定TCP端口;若是該端口上有請求到達,會在隊列上插入一個一個的任務;當這些任務被消費時,會執行connection_cb。見核心代碼片斷:
 
int main() {
// 初始化uv事件循環
loop = uv_default_loop();
uv_tcp_t server ;
struct sockaddr_in addr ;
// 指定服務器監聽地址與端口
uv_ip4_addr("192.168.132.136", 3333, &addr);
 
// 初始化TCP服務器,並與默認事件循環綁定
uv_tcp_init(loop, &server);
// 服務器端口綁定
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
// 指定鏈接處理回調函數connection_cb
// 256爲TCP等待隊列長度
int r = uv_listen((uv_stream_t*)&server, 256, connection_cb);
 
// 開始處理默認時間循環上的消息
// 若是TCP報錯,事件循環也會自動退出
return uv_run(loop, UV_RUN_DEFAULT);
}
 
connection_cb調用uv_accept會負責與發起請求的客戶端實際創建套接字,並註冊流操做回調函數read_cb:
 
void connection_cb(uv_stream_t* server, int status) {
uv_tcp_t * client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
uv_tcp_init(loop, client);
// 與客戶端創建套接字
uv_accept(server, (uv_stream_t*)client);
uv_read_start((uv_stream_t*)client, alloc_buffer, read_cb);
}
 
上文中read_cb用於讀取客戶端請求數據,併發送響應數據:
 
void read_cb(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
if (nread > 0) {
memcpy(reqBuf + bufEnd, buf->base, nread);
bufEnd += nread;
free(buf->base);
// 驗證TCP請求數據是不是合法的HTTP報文
http_parser_execute(parser, &settings, reqBuf, bufEnd);
uv_write_t * req = (uv_write_t*)malloc(sizeof(uv_write_t));
uv_buf_t * response = malloc(sizeof(uv_buf_t));
// 響應HTTP報文
response ->base = "HTTP/1.1 200 OK\r\nConnection:close\r\nContent-Length:11\r\n\r\nhello world\r\n\r\n";
response ->len = strlen(response->base);
uv_write(req, stream, response, 1, write_cb);
} else if (nread == UV_EOF) {
uv_close((uv_handle_t*)stream, close_cb);
}
}
 
所有源碼請參見simple HTTP server。咱們使用apache benchmark來作壓力測試:併發數爲5000,總請求數爲100000。
 
ab -c 5000 -n 100000 http://192.168.132.136:3333/
 
測試結果以下: 0.8秒(C) vs  5秒(Node.js)
 
 
 
咱們再看看內存佔用,0.6MB(C) vs  51MB(Node.js)
 
 
 
Node.js雖然引入了一些開銷,可是從代碼實現行數上確實要簡潔不少。
 
  本文轉自: http://www.f-z.cn/id/288
相關文章
相關標籤/搜索