4.网络操作
不了解网络编程的程序员不是好前端,而NodeJS恰好提供了一扇了解网络编程的窗口。通过NodeJS,除了可以编写一些服务端程序来协助前端开发和测试外,还能够学习一些HTTP协议与Socket协议的相关知识,这些知识在优化前端性能和排查前端故障时说不定能派上用场。本章将介绍与之相关的NodeJS内置模块。
开门红
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/
就能够看到效果。
豆知识: 在Linux系统下,监听1024以下端口需要root权限。因此,如果想监听80或443端口的话,需要使用
sudo
命令启动程序。
API走马观花
我们先大致看看NodeJS提供了哪些和网络操作有关的API。这里并不逐一介绍每个API的使用方法,官方文档已经做得很好了。
HTTP
'http'模块提供两种使用方式:
-
作为服务端使用时,创建一个HTTP服务器,监听HTTP客户端请求并返回响应。
-
作为客户端使用时,发起一个HTTP客户端请求,获取服务端响应。
首先我们来看看服务端模式下如何工作。如开门红中的例子所示,首先需要使用.createServer
方法创建一个服务器,然后调用.listen
方法监听端口。之后,每当来了一个客户端请求,创建服务器时传入的回调函数就被调用一次。可以看出,这是一种事件机制。
HTTP请求本质上是一个数据流,由请求头(headers)和请求体(body)组成。例如以下是一个完整的HTTP请求数据内容。
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.createServer(function (request, response) { var body = []; console.log(request.method); console.log(request.headers); request.on('data', function (chunk) { body.push(chunk); }); request.on('end', function () { body = Buffer.concat(body); console.log(body.toString()); }); }).listen(80); ------------------------------------ POST { 'user-agent': 'curl/7.26.0', host: 'localhost', accept: '*/*', 'content-length': '11', 'content-type': 'application/x-www-form-urlencoded' } Hello World
HTTP响应本质上也是一个数据流,同样由响应头(headers)和响应体(body)组成。例如以下是一个完整的HTTP请求数据内容。
HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 11 Date: Tue, 05 Nov 2013 05:31:38 GMT Connection: keep-alive Hello World
在回调函数中,除了可以使用response
对象来写入响应头数据外,还能把response
对象当作一个只写数据流来写入响应体数据。例如在以下例子中,服务端原样将客户端请求的请求体数据返回给客户端。
http.createServer(function (request, response) { response.writeHead(200, { 'Content-Type': 'text/plain' }); request.on('data', function (chunk) { response.write(chunk); }); request.on('end', function () { response.end(); }); }).listen(80);
接下来我们看看客户端模式下如何工作。为了发起一个客户端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
对象当作一个只读数据流来访问响应体数据。以下是一个例子。
http.get('http://www.example.com/', function (response) { var body = []; console.log(response.statusCode); console.log(response.headers); response.on('data', function (chunk) { body.push(chunk); }); response.on('end', function () { body = Buffer.concat(body); console.log(body.toString()); }); }); ------------------------------------ 200 { 'content-type': 'text/html', server: 'Apache', 'content-length': '801', date: 'Tue, 05 Nov 2013 06:08:41 GMT', connection: 'keep-alive' } <!DOCTYPE html> ...
HTTPS
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请求时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
方法解析。
http.createServer(function (request, response) { var tmp = request.url; // => "/foo/bar?a=b" url.parse(tmp); /* => { protocol: null, slashes: null, auth: null, host: null, port: null, hostname: null, hash: null, search: '?a=b', query: 'a=b', pathname: '/foo/bar', path: '/foo/bar?a=b', href: '/foo/bar?a=b' } */ }).listen(80);
.parse
方法还支持第二个和第三个布尔类型可选参数。第二个参数等于true
时,该方法返回的URL对象中,query
字段不再是一个字符串,而是一个经过querystring
模块转换后的参数对象。第三个参数等于true
时,该方法可以正确解析不带协议头的URL,例如//www.example.com/foo/bar
。
反过来,format
方法允许将一个URL对象转换为URL字符串,示例如下。
url.format({ protocol: 'http:', host: 'www.example.com', pathname: '/p/a/t/h', search: 'query=string' }); /* => 'http://www.example.com/p/a/t/h?query=string' */
另外,.resolve
方法可以用于拼接URL,示例如下。
url.resolve('http://www.example.com/foo/bar', '../baz'); /* => http://www.example.com/baz */
Query String
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
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
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(); });
灵机一点
使用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
属性来处理。
小结
本章介绍了使用NodeJS操作网络时需要的API以及一些坑回避技巧,总结起来有以下几点:
-
http
和https
模块支持服务端模式和客户端模式两种使用方式。 -
request
和response
对象除了用于读写头数据外,都可以当作数据流来操作。 -
url.parse
方法加上request.url
属性是处理HTTP请求时的固定搭配。 -
使用
zlib
模块可以减少使用HTTP协议时的数据传输量。 -
通过
net
模块的Socket服务器与客户端可对HTTP协议做底层操作。 -
小心踩坑。
相关推荐
4. 网络操作系统:可以把多台计算机联合起来,实现各台计算机之间的通信及网络中各种资源的共享。 5. 分布式操作系统:网络中各台计算机没有主次之分,在任意两台计算机间的可进展信息交换和资源共享。 四、操作...
4. 网络操作系统 5. 分布式操作系统 操作系统构造: 1. 无序构造 2. 层次构造 3. 面向对象构造 4. 对称多解决构造 5. 微内核构造 三态模型: 1. 运营态 2. 就绪态 3. 等待态 信号量与 PV 操作: 文献旳逻辑...
软件考试考级中级网络工程师复习资料合集: 0.网络工程师考试知识点[必考知识点]--必...7.网络操作系统与应用服务器配置(网络工程师笔记).docx 8.交换机与路由器的配置.docx 整理笔记.doc 整理笔记1.doc 整理笔记2.doc
4、网络操作系统。 5、结构化布线技术。 四、网络操作系统 1、操作系统的基本功能。 2、网络操作系统的基本功能。 3、了解当前流行的网络操作系统的概况。 五、Internet基础 1、Internet的基本结构与主要服务。 2、...
"网络安全操作规程完整" 网络安全操作规程是公司计算机设备及网络,系统数据,研发数据,源代码,信息安全的管理规定,以确保计算机及公司网络系统正常可靠地运行,保证各部门日常工作的顺利开展。该规程适用于本...
配置故障 3 五、网络操作系统的安全维护方法 3 1.如何保护通用操作系统的安全 3 2.网络防火墙技术 4 六、路由器网络诊断步骤与故障排除 4 1.路由器常见故障分类 4 2.网络故障诊断概述 4 七、总结 5 一、前言 计算机...
标题“802.15.4g官方协议”中所蕴含...这一标准的推出,为相关领域的无线设备制造商和网络服务商提供了统一的技术规范,有助于推动智能计量公共事业网络的标准化和互操作性,进而促进了整个行业的技术进步和市场发展。
总之,LR_03105_Patch4.zip提供的工具和功能对于APP测试人员来说是至关重要的,它能够深入到应用程序的网络层,帮助测试者确保软件的质量、性能和安全性。通过使用这类工具,可以有效地预防和解决可能出现的问题,...
"db2jcc4.jar"是IBM提供的DB2 JDBC Type 4驱动程序,它实现了Java Database Connectivity (JDBC) API,允许Java应用程序通过网络与DB2服务器进行通信。 标题中的"db2jcc4.jar下载(jdk1.8测试可用)"表明这个驱动程序...
video.js 5.18.4内置了videojs-contrib-hls.js插件,能够处理HLS流,包括不同比特率的切换,从而适应不同的网络环境,确保视频播放质量。 3. 自定义主题:video.js提供了丰富的CSS样式和JavaScript API,允许开发者...
4. **DOM(Document Object Model)操作**:MSXML4.DLL基于DOM模型,允许开发者对XML文档进行结构化的操作,如插入、删除和修改元素、属性。 5. **错误处理**:MSXML4.DLL提供了错误处理机制,当解析或操作XML时...
- **Loader**: Loader管理数据加载,如数据库查询或网络请求,确保数据在正确的线程中加载,并在数据改变时自动更新。 - **AsyncTask**: 提供了一个简单的异步任务执行框架,可以在后台线程执行耗时操作,然后在UI...
Linux 操作系统项目教程 以下是根据提供的文件信息生成的相关知识点: 1. GNU 的含义是 GNU's Not Unix 的递归缩写,指的是自由软件组织。 2. Linux 的主要部分包括内核、命令解释层和实用工具。 3. 纯种的 UNIX...
Linux内核802.11无线网络协议栈是指在Linux操作系统中实现IEEE 802.11无线局域网协议栈的设计和实现。该协议栈的主要目标是实现IEEE 802.11系列标准中的媒体访问子层(MAC)部分,并支持大部分现有的无线网卡,在所有...
2.内容:m基于matlab的IEEE802.15.4家庭网络高效节能的有效接入方法+程序操作视频 3.用处:用于家庭网络高效节能的有效接入算法编程学习 4.指向人群:本硕博等学习教研使用,企事业简单项目方案验证参考
在802.15.4标准中,MAC层的操作包括了设备如何发现彼此并建立连接,如何管理网络中数据的传输和接收,以及如何处理网络的节能模式。 IEEE 802.15.4A-2011标准也涉及了超带宽(UWB)技术。UWB是一种无线通信技术,它...
标题“ActivePerl-5.8.4.810-MSWin32-x86”指的是一款专为Windows操作系统设计的Perl解释器——ActivePerl的特定版本。Perl是一种高级的、通用的、解释型、动态的编程语言,广泛应用于文本处理、系统管理、网络编程...
2010年全国自考网络操作系统模拟试卷4.pdf
4.广域网基本原理 5.IP基本原理 6.TCP和UDP基本原理 7.路由器交换机介绍 8.命令行操作基础 9.网络设备文件管理 10.网络设备基本调试 11.交换机工作原理 12.VLAN 13.STP 14.交换机端口安全技术 15.链路聚合...