经过NodeJS,除了能够编写一些服务端程序来协助前端开发和测试外,还可以学习一些HTTP协议与Socket协议的相关知识,这些知识在优化前端性能和排查前端故障时说不定能派上用场。本章将介绍与之相关的NodeJS内置模块。html
NodeJS原本的用途是编写高性能Web服务器。咱们首先在这里重复一下官方文档里的例子,使用NodeJS内置的http
模块简单实现一个HTTP服务器。前端
var http = require('http'); http.createServer(function (request, response) { response.writeHead(200, { 'Content-Type': 'text-plain' }); response.end('Hello World\n'); }).listen(8124);
以上程序建立了一个HTTP服务器并监听8124
端口,打开浏览器访问该端口http://127.0.0.1:8124/
就可以看到效果。node
1、APIapi
一、Http:官方文档: http://nodejs.org/api/http.html浏览器
'http'模块提供两种使用方式:安全
做为服务端使用时,建立一个HTTP服务器,监听HTTP客户端请求并返回响应。服务器
做为客户端使用时,发起一个HTTP客户端请求,获取服务端响应。网络
首先咱们来看看服务端模式下如何工做。如例子所示,首先须要使用.createServer
方法建立一个服务器,而后调用.listen
方法监听端口。以后,每当来了一个客户端请求,建立服务器时传入的回调函数就被调用一次。能够看出,这是一种事件机制。并发
HTTP请求本质上是一个数据流,由请求头(headers)和请求体(body)组成。例如如下是一个完整的HTTP请求数据内容。app
POST / HTTP/1.1 User-Agent: curl/7.26.0 Host: localhost Accept: */* Content-Length: 11 Content-Type: application/x-www-form-urlencoded Hello World
能够看到,空行之上是请求头,之下是请求体。HTTP请求在发送给服务器时,能够认为是按照从头至尾的顺序一个字节一个字节地以数据流方式发送的。而http
模块建立的HTTP服务器在接收到完整的请求头后,就会调用回调函数。在回调函数中,除了可使用request
对象访问请求头数据外,还能把request
对象看成一个只读数据流来访问请求体数据。
HTTP响应本质上也是一个数据流,一样由响应头(headers)和响应体(body)组成。
接下来咱们看看客户端模式下如何工做。为了发起一个客户端HTTP请求,咱们须要指定目标服务器的位置并发送请求头和请求体,如下示例演示了具体作法。
var options = { hostname: 'www.example.com', port: 80, path: '/upload', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; var request = http.request(options, function (response) {}); request.write('Hello World'); request.end();
能够看到,.request
方法建立了一个客户端,并指定请求目标和请求头数据。以后,就能够把request
对象看成一个只写数据流来写入请求体数据和结束请求。另外,因为HTTP请求中GET
请求是最多见的一种,而且不须要请求体,所以http
模块也提供了如下便捷API。
http.get('http://www.example.com/', function (response) {});
当客户端发送请求并接收到完整的服务端响应头时,就会调用回调函数。在回调函数中,除了可使用response
对象访问响应头数据外,还能把response
对象看成一个只读数据流来访问响应体数据。
二、Https:官方文档: http://nodejs.org/api/https.html
https
模块与http
模块极为相似,区别在于https
模块须要额外处理SSL证书。
在服务端模式下,建立一个HTTPS服务器的示例以下。
var options = { key: fs.readFileSync('./ssl/default.key'), cert: fs.readFileSync('./ssl/default.cer') }; var server = https.createServer(options, function (request, response) { // ...
});
能够看到,与建立HTTP服务器相比,多了一个options
对象,经过key
和cert
字段指定了HTTPS服务器使用的私钥和公钥。
另外,NodeJS支持SNI技术,能够根据HTTPS客户端请求使用的域名动态使用不一样的证书,所以同一个HTTPS服务器可使用多个域名提供服务。接着上例,可使用如下方法为HTTPS服务器添加多组证书。
server.addContext('foo.com', { key: fs.readFileSync('./ssl/foo.com.key'), cert: fs.readFileSync('./ssl/foo.com.cer') }); server.addContext('bar.com', { key: fs.readFileSync('./ssl/bar.com.key'), cert: fs.readFileSync('./ssl/bar.com.cer') });
在客户端模式下,发起一个HTTPS客户端请求与http
模块几乎相同,示例以下。
var options = { hostname: 'www.example.com', port: 443, path: '/', method: 'GET' }; var request = https.request(options, function (response) {}); request.end();
但若是目标服务器使用的SSL证书是自制的,不是从颁发机构购买的,默认状况下https
模块会拒绝链接,提示说有证书安全问题。在options
里加入rejectUnauthorized: false
字段能够禁用对证书有效性的检查,从而容许https
模块请求开发环境下使用自制证书的HTTPS服务器。
三、Url:官方文档: http://nodejs.org/api/url.html
处理HTTP请求时url
模块使用率超高,由于该模块容许解析URL、生成URL,以及拼接URL。首先咱们来看看一个完整的URL的各组成部分。
href ----------------------------------------------------------------- host path --------------- ---------------------------- http: // user:pass @ host.com : 8080 /p/a/t/h ?query=string #hash
----- --------- -------- ---- -------- ------------- ----- protocol auth hostname port pathname search hash ------------ query
咱们可使用.parse
方法来将一个URL字符串转换为URL对象,示例以下。
url.parse('http://user:pass@host.com:8080/p/a/t/h?query=string#hash'); /* => { protocol: 'http:', auth: 'user:pass', host: 'host.com:8080', port: '8080', hostname: 'host.com', hash: '#hash', search: '?query=string', query: 'query=string', pathname: '/p/a/t/h', path: '/p/a/t/h?query=string', href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' } */
传给.parse
方法的不必定要是一个完整的URL,例如在HTTP服务器回调函数中,request.url
不包含协议头和域名,但一样能够用.parse
方法解析。
.parse
方法还支持第二个和第三个布尔类型可选参数。第二个参数等于true
时,该方法返回的URL对象中,query
字段再也不是一个字符串,而是一个通过querystring
模块转换后的参数对象。第三个参数等于true
时,该方法能够正确解析不带协议头的URL,例如//www.example.com/foo/bar
。
反过来,format
方法容许将一个URL对象转换为URL字符串。
另外,.resolve
方法能够用于拼接URL,示例以下。
url.resolve('http://www.example.com/foo/bar', '../baz'); /* => http://www.example.com/baz
*/
四、Query String:官方文档: http://nodejs.org/api/querystring.html
querystring
模块用于实现URL参数字符串与参数对象的互相转换,示例以下。
querystring.parse('foo=bar&baz=qux&baz=quux&corge'); /* => { foo: 'bar', baz: ['qux', 'quux'], corge: '' } */ querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' }); /* => 'foo=bar&baz=qux&baz=quux&corge=' */
五、Zlib:官方文档: http://nodejs.org/api/zlib.html
zlib
模块提供了数据压缩和解压的功能。当咱们处理HTTP请求和响应时,可能须要用到这个模块。
首先咱们看一个使用zlib
模块压缩HTTP响应体数据的例子。这个例子中,判断了客户端是否支持gzip,并在支持的状况下使用zlib
模块返回gzip以后的响应体数据。
http.createServer(function (request, response) { var i = 1024, data = ''; while (i--) { data += '.'; } if ((request.headers['accept-encoding'] || '').indexOf('gzip') !== -1) { zlib.gzip(data, function (err, data) { response.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Encoding': 'gzip' }); response.end(data); }); } else { response.writeHead(200, { 'Content-Type': 'text/plain' }); response.end(data); } }).listen(80);
接着咱们看一个使用zlib
模块解压HTTP响应体数据的例子。这个例子中,判断了服务端响应是否使用gzip压缩,并在压缩的状况下使用zlib
模块解压响应体数据。
var options = { hostname: 'www.example.com', port: 80, path: '/', method: 'GET', headers: { 'Accept-Encoding': 'gzip, deflate' } }; http.request(options, function (response) { var body = []; response.on('data', function (chunk) { body.push(chunk); }); response.on('end', function () { body = Buffer.concat(body); if (response.headers['content-encoding'] === 'gzip') { zlib.gunzip(body, function (err, data) { console.log(data.toString()); }); } else { console.log(data.toString()); } }); }).end();
六、Net:官方文档: http://nodejs.org/api/net.html
net
模块可用于建立Socket服务器或Socket客户端。因为Socket在前端领域的使用范围还不是很广,这里先不涉及到WebSocket的介绍,仅仅简单演示一下如何从Socket层面来实现HTTP请求和响应。
首先咱们来看一个使用Socket搭建一个很不严谨的HTTP服务器的例子。这个HTTP服务器无论收到啥请求,都固定返回相同的响应。
net.createServer(function (conn) { conn.on('data', function (data) { conn.write([ 'HTTP/1.1 200 OK', 'Content-Type: text/plain', 'Content-Length: 11', '', 'Hello World' ].join('\n')); }); }).listen(80);
接着咱们来看一个使用Socket发起HTTP客户端请求的例子。这个例子中,Socket客户端在创建链接后发送了一个HTTP GET请求,并经过data
事件监听函数来获取服务器响应。
var options = { port: 80, host: 'www.example.com' }; var client = net.connect(options, function () { client.write([ 'GET / HTTP/1.1', 'User-Agent: curl/7.26.0', 'Host: www.baidu.com', 'Accept: */*', '', '' ].join('\n')); }); client.on('data', function (data) { console.log(data.toString()); client.end(); });
2、小结
使用NodeJS操做网络,特别是操做HTTP请求和响应时会遇到一些惊喜,这里对一些常见问题作解答。
问: 为何经过headers
对象访问到的HTTP请求头或响应头字段不是驼峰的?
答: 从规范上讲,HTTP请求头和响应头字段都应该是驼峰的。但现实是残酷的,不是每一个HTTP服务端或客户端程序都严格遵循规范,因此NodeJS在处理从别的客户端或服务端收到的头字段时,都统一地转换为了小写字母格式,以便开发者能使用统一的方式来访问头字段,例如headers['content-length']
。
问: 为何http
模块建立的HTTP服务器返回的响应是chunked
传输方式的?
答: 由于默认状况下,使用.writeHead
方法写入响应头后,容许使用.write
方法写入任意长度的响应体数据,并使用.end
方法结束一个响应。因为响应体数据长度不肯定,所以NodeJS自动在响应头里添加了Transfer-Encoding: chunked
字段,并采用chunked
传输方式。可是当响应体数据长度肯定时,可以使用.writeHead
方法在响应头里加上Content-Length
字段,这样作以后NodeJS就不会自动添加Transfer-Encoding
字段和使用chunked
传输方式。
问: 为何使用http
模块发起HTTP客户端请求时,有时候会发生socket hang up
错误?
答: 发起客户端HTTP请求前须要先建立一个客户端。http
模块提供了一个全局客户端http.globalAgent
,可让咱们使用.request
或.get
方法时不用手动建立客户端。可是全局客户端默认只容许5个并发Socket链接,当某一个时刻HTTP客户端请求建立过多,超过这个数字时,就会发生socket hang up
错误。解决方法也很简单,经过http.globalAgent.maxSockets
属性把这个数字改大些便可。另外,https
模块遇到这个问题时也同样经过https.globalAgent.maxSockets
属性来处理。
http
和https
模块支持服务端模式和客户端模式两种使用方式。
request
和response
对象除了用于读写头数据外,均可以看成数据流来操做。
url.parse
方法加上request.url
属性是处理HTTP请求时的固定搭配。
使用zlib
模块能够减小使用HTTP协议时的数据传输量。
经过net
模块的Socket服务器与客户端可对HTTP协议作底层操做。