原文:https://github.com/chyingp/nodejs-learning-guide

自己敲代码:

概览

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

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

服务端例子:

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

客户端例子

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

属性/方法/事件 分类

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

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

服务端的例子

例子一:获取httpVersion/method/url

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

GET /hello HTTP/1.1
Host: 127.0.0.1:3000
Connection: keep-alive
Cache-Control: no-cache

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

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

效果如下:

1、客户端请求url:/hello
2、http版本:1.1
3、http请求方法:GET
4、http headers:{"host":"127.0.0.1:3000","connection":"keep-alive","cache-control":"no-cache","user-agent":"Mozilla/5.0

例子二:获取get请求参数

服务端代码如下:

// getClientGetQuery.js
var http = require('http');
var url = require('url');
var querystring = require('querystring'); var server = http.createServer(function(req, res){
var urlObj = url.parse(req.url);
var query = urlObj.query;
var queryObj = querystring.parse(query); console.log( JSON.stringify(queryObj) ); res.end('ok');
}); server.listen(3000);

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

服务端输出如下

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

例子三:获取post请求参数

服务端代码如下

// getClientPostBody.js
var http = require('http');
var url = require('url');
var querystring = require('querystring'); var server = http.createServer(function(req, res){ var body = '';
req.on('data', function(thunk){
body += thunk;
}); req.on('end', function(){
console.log( 'post body is: ' + body );
res.end('ok');
});
}); server.listen(3000);

通过curl构造post请求:

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

服务端打印如下:

post body is: nick=casper&hello=world

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

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

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

客户端处例子

例子一:获取httpVersion/statusCode/statusMessage

代码如下:

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

控制台输出:

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

事件对比:aborted、close

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

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

服务端表现

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

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

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

测试代码如下:

var http = require('http');

var server = http.createServer(function(req, res){

    console.log('1、收到客户端请求: ' + req.url);

    req.on('aborted', function(){
console.log('2、客户端请求aborted');
}); req.on('close', function(){
console.log('3、客户端请求close');
}); // res.end('ok'); 故意不返回,等着客户端中断请求
}); server.listen(3000, function(){
var client = http.get('http://127.0.0.1:3000/aborted');
setTimeout(function(){
client.abort(); // 故意延迟100ms,确保请求发出
}, 100);
}); // 输出如下
// 1、收到客户端请求: /aborted
// 2、客户端请求aborted
// 3、客户端请求close

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

  function abortIncoming() {
while (incoming.length) {
var req = incoming.shift();
req.emit('aborted');
req.emit('close');
}
// abort socket._httpMessage ?
}

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

// reqSocketClose.js
var http = require('http'); var server = http.createServer(function(req, res){ console.log('server: 收到客户端请求'); req.on('close', function(){
console.log('server: req close');
}); req.socket.on('close', function(){
console.log('server: req.socket close');
}); res.end('ok');
}); server.listen(3000); var client = http.get('http://127.0.0.1:3000/aborted', function(res){
console.log('client: 收到服务端响应');
});

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

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

客户端表现

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

  • 没有res.pipe(process.stdout):close不触发。
  • res.pipe(process.stdout):close触发。
var http = require('http');

var server = http.createServer(function(req, res){

    console.log('1、服务端:收到客户端请求');

    res.flushHeaders();
res.setTimeout(100); // 故意不返回,3000ms后超时
}); server.on('error', function(){}); server.listen(3000, function(){ var client = http.get('http://127.0.0.1:3000/aborted', function(res){ console.log('2、客户端:收到服务端响应'); // res.pipe(process.stdout); 注意这行代码 res.on('aborted', function(){
console.log('3、客户端:aborted触发!');
}); res.on('close', function(){
console.log('4、客户端:close触发!');
});
});
});

信息量略大的 .destroy()

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

测试代码如下:

var http = require('http');

var server = http.createServer(function(req, res){

    console.log('服务端:收到客户端请求');

    req.destroy(new Error('测试destroy'));

    req.on('error', function(error){
console.log('服务端:req error: ' + error.message);
}); req.socket.on('error', function(error){
console.log('服务端:req socket error: ' + error.message);
})
}); server.on('error', function(error){
console.log('服务端:server error: ' + error.message);
}); server.listen(3000, function(){ var client = http.get('http://127.0.0.1:3000/aborted', function(res){
// do nothing
}); client.on('error', function(error){
console.log('客户端:client error触发!' + error.message);
});
});

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

服务端:收到客户端请求
服务端:req socket error: 测试destroy
客户端: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

NodeJS学习笔记 (5)网络服务-http-req(ok)的更多相关文章

  1. NodeJS学习笔记 (9)网络服务-https(ok)

    模块概览 这个模块的重要性,基本不用强调了.在网络安全问题日益严峻的今天,网站采用HTTPS是个必然的趋势. 在nodejs中,提供了 https 这个模块来完成 HTTPS 相关功能.从官方文档来看 ...

  2. NodeJS学习笔记 (8)网络服务-http-server(ok)

    http服务端概览 创建server 几行代码搞定 var http = require('http'); var requestListener = function(req, res){ res. ...

  3. NodeJS学习笔记 (4)网络服务-http(ok)

    原文:https://github.com/chyingp/nodejs-learning-guide 自己敲代码: http模块概览 大多数nodejs开发者都是冲着开发web server的目的选 ...

  4. NodeJS学习笔记 (7)网络服务-http-client(ok)

    原文:https://github.com/chyingp/nodejs-learning-guide 自己敲代码: ClientRequest概览 当你调用 http.request(options ...

  5. NodeJS学习笔记 (6)网络服务-http-res(ok)

    原文:https://github.com/chyingp/nodejs-learning-guide 自己敲代码: 概览 http模块四剑客之一的res,应该都不陌生了.一个web服务程序,接受到来 ...

  6. nodejs学习笔记之网络编程

    了解一下OSI七层模型   OSI层 功能 TCP/IP协议 应用层 文件传输,电子邮件,文件服务,虚拟终端  TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 表示层 数据格式化 ...

  7. NodeJS学习笔记 (10)网络TCP-net(ok)

    模块概览 net模块是同样是nodejs的核心模块.在http模块概览里提到,http.Server继承了net.Server,此外,http客户端与http服务端的通信均依赖于socket(net. ...

  8. NodeJS学习笔记 (12)网络地址解析-url(ok)

    模块概述 nodejs中,提供了url这个非常实用的模块,用来做URL的解析.在做node服务端的开发时会经常用到.使用很简单,总共只有3个方法. 正式讲解前,各位同学先把下面这个图记在心上(来自no ...

  9. NodeJS学习笔记 (11)网络UDP-dgram(ok)

    模块概览 dgram模块是对UDP socket的一层封装,相对net模块简单很多,下面看例子. UPD客户端 vs UDP服务端 首先,启动UDP server,监听来自端口33333的请求. se ...

随机推荐

  1. 【AnjularJS系列6 】 过滤器

    第六篇,过滤器 AngularJS 过滤器可用于转换数据: 过滤器 描述 currency 格式化数字为货币格式. filter 从数组项中选择一个子集. lowercase 格式化字符串为小写. o ...

  2. Pyhton学习——Day7

    ##############################################匿名函数################################################## ...

  3. node——含有异步函数的函数封装

    在写代码时我们会发现有大量的重复代码,为了使代码更加简洁,我们可以将重复的代码封装为一个可以在多个部分时候用的函数. 之前写的新闻代码中,经常出现的操作有对文件的读取,我们可以将它封装为一个函数rea ...

  4. Linux 环境中从源代码编译安装 ReText 问题与解决

    从源代码编译安装 ReText 问题与解决 1. 如何安装 Python Markups 1.1 从 https://launchpad.net/python-markups 下载 Python Ma ...

  5. 博客模板更新CSS

    采用了作者#a的模板BlueSky进行了些许修改 在原有基础上加了三个样式,使页面显示风格统一些 #home{ background-color:#fff; } #main{ background-c ...

  6. "啃下"插入排序

    插入排序法基本原理 插入排序法较冒泡排序法和选择排序法更贴近生活,应该来说理解起来更快.如果你现在能够得到一副麻将,请把里面的“一万”到“六万”拿出来,打乱顺序,再重新排好,就像打麻将开始那样.是否需 ...

  7. [AtCoder Grand Contest 024 Problem E]Sequence Growing Hard

    题目大意:考虑 N +1 个数组 {A0,A1,…,AN}.其中 Ai 的长度是 i,Ai 内的所有数字都在 1 到 K 之间. Ai−1 是 Ai 的子序列,即 Ai 删一个数字可以得到 Ai−1. ...

  8. 安装SSH、配置SSH无密码登录 ssh localhost

    集群.单节点模式都需要用到 SSH 登陆(类似于远程登陆,你可以登录某台 Linux 主机,并且在上面运行命令),Ubuntu 默认已安装了 SSH client,此外还需要安装 SSH server ...

  9. Android开发进度03

    1,今日:目标:实现登录界面 2,昨天:实现第一个Android项目Helloworld 3,收获:会使用手机进行测试,会使用SQlite数据库 4,问题:创建项目时出错

  10. Linux系统串口接收数据编

    http://blog.csdn.net/bg2bkk/article/details/8668576 之前基于IBM deveplopworks社区的代码,做了串口初始化和发送的程序,今天在此基础上 ...