Node.js之http模块
一、http服务端概览
创建server
//1.获取http内置对象 var http = require('http'); //2.定义回调函数 var requestListener = function (req, res) { res.end('ok'); }; //3.创建服务器 var server = http.createServer(requestListener); // var server = new http.Server(requestListener); 跟上面是等价的 //4.添加监听端口 server.listen(3000);
获取请求方信息
⏰:HTTP版本、HTTP method、headers、url
var http = require('http') var serve = http.createServer((req, res) => { console.log('客户端请求Url:' + req.url); console.log('http版本:' + req.httpVersion); console.log('http请求方法:' + req.method); console.log('http headers:' + req.headers); res.end('ok') }) serve.listen(3000);
效果如下:
客户端请求url:/hello http版本:1.1 http请求方法:GET http headers:{"host":"127.0.0.1:3000","connection":"keep-alive","cache-control":"max-age=0","upgrade-insecure-requests":"1","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","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","accept-encoding":"gzip, deflate, sdch, br","accept-language":"zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4"}
⏰ 获取get请求参数
var http = require('http') var url = require('url') var querystring = require('querystring') //创建服务器 var server = http.createServer((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);
运行如下命令
curl http://127.0.0.1:3000/hello\?nick\=chyingp\&hello\=world
服务端输出如下
{"nick":"chyingp","hello":"world"}
⏰ 获取post请求参数
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请求中,不同的Content-type,post body有不小差异,感兴趣的同学可以自己试下。
post body is: nick=casper&hello=world
比如本例中的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
枯燥的事件
首先,我们来看下有哪些事件
🔊 checkContinue、checkExpectation、clientError、close、connect、connection、request、upgrade
var http = require('http'); var PORT = 3000; var noop = function () { }; var svr = http.createServer(noop); var anotherSvr = http.createServer(noop); anotherSvr.on('error', function (e) { console.error('出错啦!' + e.message); }); svr.listen(PORT, function () { anotherSvr.listen(PORT); });
运行代码,输出如下
出错啦!listen EADDRINUSE :::3000
⏰ connect vs connection
两者差别非常大,虽然字眼看着有点像。
- connect:当客户端的HTTP method为connect时触发。
- connection:当TCP连接建立时触发,大部分时候可以忽略这个事件(目测模块内部自己用到而已)。此外,可以通过 req.connection 来获取这个socket(从nodejs源码来看,req.socket、req.connection 都指向了这个socket)。此外,socket上的readable事件不会触发(具体原因请看模块内部实现,反正我是还没研究)
大部分时候都不会用到,除非你要开发HTTP代理。当客户端发起 connect 请求时触发(注意绕过了 requestListener)
var http = require('http'); var PORT = 3000; var server = http.createServer(function (req, res) { res.end('ok'); }); // 注意:发起connect请求的例子在 ./httpServerEventConnectClient.js 里 server.on('connect', function (req, socket, head) { console.log('connect事件触发'); socket.end(); // 反正我就只想举个例子,没打算正经处理。。。 }); server.listen(PORT);
⏰ request
当有新的连接到来时触发。那跟 connection 有什么区别呢?
好了,keep-alive闪亮登场!在持久化连接的情况下,多个 request 可能对应的是 一个 connection。
先来看下没有keep-alive的场景
var http = require('http'); var PORT = 3000; var requestIndex = 0; var connectionIndex = 0; var server = http.createServer(function(req, res){ res.end('ok'); }); server.on('request', function(req, res){ requestIndex++; console.log('request event: 第'+ requestIndex +'个请求!'); }); server.on('connection', function(req, res){ connectionIndex++; console.log('connection event: 第'+ connectionIndex +'个请求!'); }); server.listen(PORT);
通过curl连续发送3个请求,看下效果
for i in `seq 1 3`; do curl http://127.0.0.1:3000; done
服务端输出如下
connection event: 第1个请求! request event: 第1个请求! connection event: 第2个请求! request event: 第2个请求! connection event: 第3个请求! request event: 第3个请求!
然后,再来看下有keep-alive的场景。用 postman 构造包含 keep-alive 的请求,最终的HTTP请求报文如下
GET / HTTP/1.1 Host: 127.0.0.1:3000 Connection: keep-alive Cache-Control: no-cache Postman-Token: 6027fda7-f936-d3ac-e54f-dafcbf5e58ff
连续发送3个请求,服务端打印日志如下
connection event: 第1个请求! request event: 第1个请求! request event: 第2个请求! request event: 第3个请求!
二、http客户端概览
当你调用 http.request(options) 时,会返回 ClientRequest实例,主要用来创建HTTP客户端请求。
简单的GET请求
下面构造了个GET请求,访问 http://id.qq.com/ ,并将返回的网页内容打印在控制台下。
var http = require('http'); var options = { protocol: 'http:', hostname: 'id.qq.com', port: '80', path: '/', method: 'GET' }; var client = http.request(options, function (res) { var data = ''; res.setEncoding('utf8'); res.on('data', function (chunk) { data += chunk; }); res.on('end', function () { console.log(data); }); }); client.end();
当然,也可以用便捷方法 http.get(options) 进行重写
var http = require('http'); http.get('http://id.qq.com/', function(res){ var data = ''; res.setEncoding('utf8'); res.on('data', function(chunk){ data += chunk; }); res.on('end', function(){ console.log(data); }); });
简单的post请求
在下面例子中,首先创建了个http server,负责将客户端发送过来的数据回传。
接着,创建客户端POST请求,向服务端发送数据。需要注意的点有:
- method 指定为 POST。
- headers 里声明了 content-type 为 application/x-www-form-urlencoded。
- 数据发送前,用 querystring.stringify(obj) 对传输的对象进行了格式化。
var http = require('http'); var querystring = require('querystring'); var createClientPostRequest = function () { var options = { method: 'POST', protocol: 'http:', hostname: '127.0.0.1', port: '3000', path: '/post', headers: { "connection": "keep-alive", "content-type": "application/x-www-form-urlencoded" } }; // 发送给服务端的数据 var postBody = { nick: 'chyingp' }; // 创建客户端请求 var client = http.request(options, function (res) { // 最终输出:Server got client data: nick=chyingp res.pipe(process.stdout); }); // 发送的报文主体,记得先用 querystring.stringify() 处理下 client.write(querystring.stringify(postBody)); client.end(); }; // 服务端程序,只是负责回传客户端数据 var server = http.createServer(function (req, res) { res.write('Server got client data: '); req.pipe(res); }); server.listen(3000, createClientPostRequest);
各种事件
在官方文档里,http.RequestClient相关的事件共有7个。跟HTTP协议密切相关的有3个,分别是 connect、continue、upgrade,其他4个分别是 abort、aborted、socket、response。
- 其他:abort、aborted、socket、response
- 与HTTP协议相关:connect、continue、upgrade
跟HTTP协议相关的会相对复杂些,因为涉及HTTP协议的设计细节。其他3个相对简单。下面分别进行简单的介绍。
⏰ response事件
最容易理解的一个,当收到来自服务端的响应时触发,其实跟 http.get(url, cbk) 中的回调是一样的,看下程序运行的打印信息就知道。
var http = require('http'); var url = 'http://id.qq.com/'; var client = http.get(url, function (res) { console.log('1. response event'); }); client.on('response', function (res) { console.log('2. response event'); }); client.end();
打印信息:
1. response event 2. response event
⏰ socket事件
当给client分配socket的时候触发,如果熟悉net模块对这个事件应该不陌生。大部分时候并不需要关注这个事件,虽然内部其实挺复杂的。
⏰ abort/aborted 事件
这两个事件看着非常像,都是请求中断时触发,差异在于中断的发起方:
- abort:客户端主动中断请求(第一次调用 client.abort() 时触发)
- aborted:服务端主动中断请求,且请求已经中断时触发。
⏰ continue事件
当收到服务端的响应 100 Continue 时触发。熟悉HTTP协议的同学应该对 100 Continue 有所了解。当客户端向服务端发送首部 Expect: 100-continue ,服务端经过一定的校验后,决定对客户端的后续请求放行,于是返回返回 100 Continue,知会客户端,可以继续发送数据。(request body)
⏰ upgrade事件
同样是跟HTTP协议密切相关。当客户端向客户端发起请求时,可以在请求首部里声明 'Connection': 'Upgrade' ,以此要求服务端,将当前连接升级到新的协议。如果服务器同意,那么就升级协议继续通信。这里不打算展开太多细节,直接上官方文档的代码
const http = require('http'); // Create an HTTP server var srv = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('okay'); }); srv.on('upgrade', (req, socket, head) => { socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + 'Upgrade: WebSocket\r\n' + 'Connection: Upgrade\r\n' + '\r\n'); socket.pipe(socket); // echo back }); // now that server is running srv.listen(1337, '127.0.0.1', () => { // make a request var options = { port: 1337, hostname: '127.0.0.1', headers: { 'Connection': 'Upgrade', 'Upgrade': 'websocket' } }; var req = http.request(options); req.end(); req.on('upgrade', (res, socket, upgradeHead) => { console.log('got upgraded!'); socket.end(); process.exit(0); }); });
其他
除了上面讲解到的属性、方法、事件外,还有下面方法没有讲到。并不是它们不重要,篇幅有限,后面再展开。
- client.abort():中断请求;
- client.setTimeout(timeout):请求超时设置;
- client.flushHeaders() 及早将请求首部发送出去;
- client.setSocketKeepAlive():当内部分配 socket 并连接上时,就会内部调用 socket.keepAlive();
- client.setNoDelay([noDelay]):当内部分配 socket 并连接上时,就会内部调用 socket.setNoDelay();
浙公网安备 33010602011771号