概览

本文的重点会放在req这个对象上。前面已经提到,它其实是http.IncomingMessage实例,在服务端、客户端作用略微有差异

  • 服务端处:获取请求方的相关信息,如request header等。
  • 客户端处:获取响应方返回的相关信息,如statusCode等。

服务端例子:

  1. // 下面的 req
  2. var http = require('http');
  3. var server = http.createServer(function(req, res){
  4. console.log(req.headers);
  5. res.end('ok');
  6. });
  7. server.listen(3000);

客户端例子

  1. // 下面的res
  2. var http = require('http');
  3. http.get('http://127.0.0.1:3000', function(res){
  4. console.log(res.statusCode);
  5. });

属性/方法/事件 分类

http.IncomingMessage的属性/方法/事件 不是特别多,按照是否客户端/服务端 特有的,下面进行简单归类。可以看到

  • 服务端处特有:url
  • 客户端处特有:statusCode、statusMessage
类型 名称 服务端 客户端
事件 aborted
事件 close
属性 headers
属性 rawHeaders
属性 statusCode
属性 statusMessage
属性 httpVersion
属性 httpVersion
属性 url
属性 socket
方法 .destroy()
方法 .setTimeout()

服务端的例子

例子一:获取httpVersion/method/url

下面是一个典型的HTTP请求报文,里面最重要的内容包括:HTTP版本、请求方法、请求地址、请求头部。

  1. GET /hello HTTP/1.1
  2. Host: 127.0.0.1:3000
  3. Connection: keep-alive
  4. Cache-Control: no-cache

那么,如何获取上面提到的信息呢?很简单,直接上代码

  1. // getClientInfo.js
  2. var http = require('http');
  3. var server = http.createServer(function(req, res){
  4. console.log( '1、客户端请求url:' + req.url );
  5. console.log( '2、http版本:' + req.httpVersion );
  6. console.log( '3、http请求方法:' + req.method );
  7. console.log( '4、http请求头部' + JSON.stringify(req.headers) );
  8. res.end('ok');
  9. });
  10. server.listen(3000);

效果如下:

  1. 1、客户端请求url:/hello
  2. 2http版本:1.1
  3. 3http请求方法:GET
  4. 4http headers:{"host":"127.0.0.1:3000","connection":"keep-alive","cache-control":"no-cache","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36","postman-token":"1148986a-ddfb-3569-e2c0-585634655fe4","accept":"*/*","accept-encoding":"gzip, deflate, sdch, br","accept-language":"zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4"}

例子二:获取get请求参数

服务端代码如下:

  1. // getClientGetQuery.js
  2. var http = require('http');
  3. var url = require('url');
  4. var querystring = require('querystring');
  5. var server = http.createServer(function(req, res){
  6. var urlObj = url.parse(req.url);
  7. var query = urlObj.query;
  8. var queryObj = querystring.parse(query);
  9. console.log( JSON.stringify(queryObj) );
  10. res.end('ok');
  11. });
  12. server.listen(3000);

访问地址 http://127.0.0.1:3000/hello?nick=chyingp&hello=world

服务端输出如下

  1. {"nick":"chyingp","hello":"world"}

例子三:获取post请求参数

服务端代码如下

  1. // getClientPostBody.js
  2. var http = require('http');
  3. var url = require('url');
  4. var querystring = require('querystring');
  5. var server = http.createServer(function(req, res){
  6. var body = '';
  7. req.on('data', function(thunk){
  8. body += thunk;
  9. });
  10. req.on('end', function(){
  11. console.log( 'post body is: ' + body );
  12. res.end('ok');
  13. });
  14. });
  15. server.listen(3000);

通过curl构造post请求:

  1. curl -d 'nick=casper&hello=world' http://127.0.0.1:3000

服务端打印如下:

  1. post body is: nick=casper&hello=world

备注:post请求中,不同的Content-type,post body有不小差异,感兴趣的同学可以研究下。

本例中的post请求,HTTP报文大概如下

  1. POST / HTTP/1.1
  2. Host: 127.0.0.1:3000
  3. Content-Type: application/x-www-form-urlencoded
  4. Cache-Control: no-cache
  5. nick=casper&hello=world

客户端处例子

例子一:获取httpVersion/statusCode/statusMessage

代码如下:

  1. var http = require('http');
  2. var server = http.createServer(function(req, res){
  3. res.writeHead(200, {'content-type': 'text/plain',});
  4. res.end('from server');
  5. });
  6. server.listen(3000);
  7. var client = http.get('http://127.0.0.1:3000', function(res){
  8. console.log('1、http版本:' + res.httpVersion);
  9. console.log('2、返回状态码:' + res.statusCode);
  10. console.log('3、返回状态描述信息:' + res.statusMessage);
  11. console.log('4、返回正文:');
  12. res.pipe(process.stdout);
  13. });

控制台输出:

  1. 1http版本:1.1
  2. 2、返回状态码:200
  3. 3、返回状态描述信息:OK
  4. 4、返回正文:
  5. from server

事件对比:aborted、close

官方文档对这两个事件的解释是:当客户端终止请求时,触发aborted事件;当客户端连接断开时,触发close事件;官方文档传送们:地址

解释得比较含糊,从实际实验对比上来看,跟官方文档有不小出入。此外,客户端处、服务端处的表现也是不同的。

服务端表现

根据实际测试结果来看,当客户端:

  • abort请求时,服务端req的aborted、close事件都会触发;(诡异)
  • 请求正常完成时,服务端req的close事件不会触发;(也很诡异)

直接扒了下nodejs的源代码,发现的确是同时触发的,触发场景:请求正常结束前,客户端abort请求。

测试代码如下:

  1. var http = require('http');
  2. var server = http.createServer(function(req, res){
  3. console.log('1、收到客户端请求: ' + req.url);
  4. req.on('aborted', function(){
  5. console.log('2、客户端请求aborted');
  6. });
  7. req.on('close', function(){
  8. console.log('3、客户端请求close');
  9. });
  10. // res.end('ok'); 故意不返回,等着客户端中断请求
  11. });
  12. server.listen(3000, function(){
  13. var client = http.get('http://127.0.0.1:3000/aborted');
  14. setTimeout(function(){
  15. client.abort(); // 故意延迟100ms,确保请求发出
  16. }, 100);
  17. });
  18. // 输出如下
  19. // 1、收到客户端请求: /aborted
  20. // 2、客户端请求aborted
  21. // 3、客户端请求close

以下代码来自nodejs源码(_http_server.js)

  1. function abortIncoming() {
  2. while (incoming.length) {
  3. var req = incoming.shift();
  4. req.emit('aborted');
  5. req.emit('close');
  6. }
  7. // abort socket._httpMessage ?
  8. }

再来一波对比,req.on('close')req.socket.on('close')

  1. // reqSocketClose.js
  2. var http = require('http');
  3. var server = http.createServer(function(req, res){
  4. console.log('server: 收到客户端请求');
  5. req.on('close', function(){
  6. console.log('server: req close');
  7. });
  8. req.socket.on('close', function(){
  9. console.log('server: req.socket close');
  10. });
  11. res.end('ok');
  12. });
  13. server.listen(3000);
  14. var client = http.get('http://127.0.0.1:3000/aborted', function(res){
  15. console.log('client: 收到服务端响应');
  16. });

输出如下,正儿八经的close事件触发了。

  1. server: 收到客户端请求
  2. server: req.socket close
  3. client: 收到服务端响应

客户端表现

猜测客户端的aborted、close也是在类似场景下触发,测试代码如下。发现一个比较有意思的情况,res.pipe(process.stdout) 这行代码是否添加,会影响close是否触发。

  • 没有res.pipe(process.stdout):close不触发。
  • res.pipe(process.stdout):close触发。
  1. var http = require('http');
  2. var server = http.createServer(function(req, res){
  3. console.log('1、服务端:收到客户端请求');
  4. res.flushHeaders();
  5. res.setTimeout(100); // 故意不返回,3000ms后超时
  6. });
  7. server.on('error', function(){});
  8. server.listen(3000, function(){
  9. var client = http.get('http://127.0.0.1:3000/aborted', function(res){
  10. console.log('2、客户端:收到服务端响应');
  11. // res.pipe(process.stdout); 注意这行代码
  12. res.on('aborted', function(){
  13. console.log('3、客户端:aborted触发!');
  14. });
  15. res.on('close', function(){
  16. console.log('4、客户端:close触发!');
  17. });
  18. });
  19. });

信息量略大的 .destroy()

经过前面aborted、close的摧残,本能的觉得 .destroy() 方法的表现会有很多惊喜之处。

测试代码如下:

  1. var http = require('http');
  2. var server = http.createServer(function(req, res){
  3. console.log('服务端:收到客户端请求');
  4. req.destroy(new Error('测试destroy'));
  5. req.on('error', function(error){
  6. console.log('服务端:req error: ' + error.message);
  7. });
  8. req.socket.on('error', function(error){
  9. console.log('服务端:req socket error: ' + error.message);
  10. })
  11. });
  12. server.on('error', function(error){
  13. console.log('服务端:server error: ' + error.message);
  14. });
  15. server.listen(3000, function(){
  16. var client = http.get('http://127.0.0.1:3000/aborted', function(res){
  17. // do nothing
  18. });
  19. client.on('error', function(error){
  20. console.log('客户端:client error触发!' + error.message);
  21. });
  22. });

输出如下。根据 .destroy() 调用的时机不同,error 触发的对象不同。(测试过程比较枯燥,有时间再总结一下)

  1. 服务端:收到客户端请求
  2. 服务端:req socket error: 测试destroy
  3. 客户端:client error触发!socket hang up

不常用属性

  • rawHeaders:未解析前的request header。
  • trailers:在分块传输编码(chunk)中用到,表示trailer后的header可分块传输。(感兴趣的可以研究下)
  • rawTrailers:

关于trailers属性:

The request/response trailers object. Only populated at the ‘end’ event.

写在后面

一个貌似很简单的对象,实际比想的要复杂一些。做了不少对比实验,也发现了一些好玩的东西,打算深入学习的同学可以自己多动手尝试一下 :)

TODO:

  1. 对close、aborted进行更深入对比
  2. 对.destroy()进行更深入对比

相关链接

官方文档:
https://nodejs.org/api/http.html#http_class_http_incomingmessage