node系列3
网络操作
通过NodeJS,除了可以编写一些服务端程序来协助前端开发和测试外,还能够学习一些HTTP协议与Socket协议的相关知识
开门红
使用NodeJS内置的http
模块简单实现一个HTTP服务器
var http = require('http'); http.createServer(function (request, response) {
response.writeHead(, { 'Content-Type': 'text-plain' });
response.end('Hello World\n');
}).listen();
以上程序创建了一个HTTP服务器并监听8124
端口,打开浏览器访问该端口http://127.0.0.1:8124/
就能够看到效果
在Linux系统下,监听1024以下端口需要root权限。因此,如果想监听80或443端口的话,需要使用sudo
命令启动程序
API走马观花
我们先大致看看NodeJS提供了哪些和网络操作有关的API http://nodejs.org/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/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();
});
灵机一点
- 问: 为什么通过
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
属性来处理
node系列3的更多相关文章
- 深入理解Node系列-细说Connect(上)
前言 想必对于广大前后端的同学们,Node 或是用来作为网站服务器的搭建,亦或是用来作为开发脚手架的运用,或是早有套路,亦或是浅尝辄止.从现在开始博主将会不定时的对 Node 系列的产品做分析,其中夹 ...
- node系列1
NodeJS基础 JS是脚本语言,脚本语言都需要一个解析器才能运行,NodeJS就是一个解析器.nodejs.org 打开终端,键入node进入命令交互模式,可以输入一条代码语句后立即执行并显示结果 ...
- node系列:琐碎备忘
cmd 全局与本地路径 查看:默认 查看本地路径:npm config get cache,默认和nodejs安装目录同一目录 查看全局路径:npm config get prefix,默认c盘app ...
- node系列4
进程管理 NodeJS可以感知和控制自身进程的运行环境和状态,也可以创建子进程并与其协同工作,这使得NodeJS可以把多个程序组合在一起共同完成某项工作,并在其中充当胶水和调度器的作用.本章除了介绍与 ...
- 每天几分钟跟小猫学前端之node系列:用node实现最简单的爬虫
先来段求分小视频: https://www.iesdouyin.com/share/video/6550631947750608142/?region=CN&mid=6550632036246 ...
- node系列:全局与本地
查看:默认和当前的 全局与本地 全局路径:npm config get prefix 本地路径:npm config get cache 修改 修改就会创建对应目录(文件夹) 修改本地路径:npm c ...
- 带你学Node系列之express-CRUD
前言 hello,小伙伴们,我是你们的pubdreamcc,本篇博文出至于我的GitHub仓库node学习教程资料,欢迎小伙伴们点赞和star,你们的点赞是我持续更新的动力. GitHub仓库地址:n ...
- 每天学点node系列-stream
在编写代码时,我们应该有一些方法将程序像连接水管一样连接起来 -- 当我们需要获取一些数据时,可以去通过"拧"其他的部分来达到目的.这也应该是IO应有的方式. -- Doug Mc ...
- 每天学点node系列-http
任何可以使用JavaScript来编写的应用,最终会由JavaScript编写.--Atwood's Law http模块概览 http模块主要用于创建http server服务,并且 支持更多特性 ...
- 每天学点node系列-fs文件系统
好的代码像粥一样,都是用时间熬出来的. 概述 文件 I/O 是由简单封装的标准 POSIX 函数提供的. 通过 require('fs') 使用该模块. 所有文件系统操作都具有同步和异步的形式. 异步 ...
随机推荐
- Oracle分析函数之FIRST_VALUE和LAST_VALUE
FIRST_VALUE 返回组中数据窗口的第一个值 FIRST_VALUE ( [scalar_expression )OVER ( [ partition_by_clause ] order_by_ ...
- TypeScript学习指南第二章--接口(Interface)
接口(Interface) TypeScript的核心机制之一在于它的类型检查系统(type-checker)只关注一个变量的"模型(shape)" 稍后我们去了解这个所谓的形状是 ...
- MongoDB索引介绍
MongoDB中的索引其实类似于关系型数据库,都是为了提高查询和排序的效率的,并且实现原理也基本一致.由于集合中的键(字段)可以是普通数据类型,也可以是子文档.MongoDB可以在各种类型的键上创建索 ...
- csuoj 1353: Guessing the Number
这个题我想到要用kmp找到循环节: 但是后面的我就不会做了: 看到题解才知道是字符串的最小表示: #include<cstdio> #include<cstring> #inc ...
- 关于JDNI、JMX
http://www.cnblogs.com/itech/archive/2010/09/16/1827999.html http://javacrazyer.iteye.com/blog/75948 ...
- How to Cracked Sublime Text 3 Build 3065 in Ubuntu (Linux)
整理自How to Cracked Sublime Text 3 Build 3065 in Ubuntu (Linux) Sublime Text 3 Build 3065 Release Date ...
- POJ1177+线段树+扫描线
思路: 以y的值进行离散化 根据x的值 对每一条y轴边进行处理,如果是"左边"则插入,是"右边"则删除. /* 扫描线+线段树+离散化 求多个矩形的周长 */ ...
- UVALive - 4287 Proving Equivalences
给定n个命题之间的已经证明的关系如 a b表示已经证明蕴含式a→b,要求还需要再作多少次证明使得所有的命题都是等价的.将每个命题看成一个点,已经证明的命题之间连一条边,问题转化为添加多少条单向边使得图 ...
- is present but cannot be translated into a null value due to being declared as a primitive type
解决办法:把基本类型改为对象,譬如此处将pageId的类型由int 改为Integer 2016-10-19 19:36:11.275 DEBUG [http-nio-9999-exec-2][org ...
- 用APP赚钱(转)
英文原文:MAKING MONEY ON APPS 做为半个 iOS 开发的一家公司,我时不时地考虑如何用 APP 赚钱.最近由Brent Simmons 和 Jared Sinclair 的文章挑起 ...