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
相关文章
相关标签/搜索