在 NodeJS 中用来建立服务的模块是 http
核心模块,本篇就来介绍关于使用 http
模块搭建 HTTP 服务器和客户端的方法,以及模块的基本 API。html
在 NodeJS 中,建立 HTTP 服务器能够与 net
模块建立 TCP 服务器对比,建立服务器有也两种方式。浏览器
方式 1:bash
1
2
3
4
5
6
7
复制代码 |
const http = require("http");
const server = http.createServer(function(req, res) {
// ......
});
server.listen(3000);
复制代码 |
方式 2:服务器
1
2
3
4
5
6
7
8
9
复制代码 |
const http = require("http");
const server = http.createServer();
server.on("request", function(req, res) {
// ......
});
server.listen(3000);
复制代码 |
在 createServer
的回调和 request
事件的回调函数中有两个参数,req
(请求)、res
(响应),基于 socket
,这两个对象都是 Duplex 类型的可读可写流。并发
http
模块是基于 net
模块实现的,因此 net
模块原有的事件在 http
中依然存在。socket
1
2
3
4
5
6
7
8
9
10
复制代码 |
const http = require("http");
const server = http.createServer();
// net 模块事件
server.on("connection", function(socket) {
console.log("链接成功");
});
server.listen(3000);
复制代码 |
在请求对象 req
中存在请求的方法、请求的 url
(包含参数,即查询字符串)、当前的 HTTP 协议版本和请求头等信息。函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
复制代码 |
const http = require("http");
const server = http.createServer();
server.on("request", function(req, res) {
console.log(req.method); // 获取请求方法
console.log(req.url); // 获取请求路径(包含查询字符串)
console.log(req.httpVersion); // 获取 HTTP 协议版本
console.log(req.headers); // 获取请求头(对象)
// 获取请求体的内容
let arr = [];
req.on("data", function(data) {
arr.push(data);
});
req.on("end", function() {
console.log(Buffer.concat(arr).toString());
});
});
server.listen(3000, function() {
console.log("server start 3000");
});
复制代码 |
经过 req
对应的属性能够拿到请求行和请求首部的信息,请求体内的内容经过流操做来获取,其中 url
中存在多个有用的参数,咱们本身处理会很麻烦,能够经过 NodeJS 的核心模块 url
进行解析。ui
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码 |
const url = require("url");
let str = "http://user:pass@www.pandashen.com:8080/src/index.html?a=1&b=2#hash";
// parse 方法帮助咱们解析 url 路径
let obj = url.parse(str, true);
console.log(obj);
// {
// protocol: 'http:',
// slashes: true,
// auth: 'user:pas',
// host: 'www.pandashen.com:8080',
// port: '8080',
// hostname: 'www.pandashen.com',
// hash: '#hash',
// search: '?a=1&b=2',
// query: '{ a: '1', b: '2' }',
// pathname: '/src/index.html'
// path: '/src/index.html?a=1&b=2',
// href: 'http://user:pass@www.pandashen.com:8080/src/index.html?a=1&b=2#hash' }
复制代码 |
在被解析路径返回的对象中有几个属性被常常使用:编码
咱们使用 url
的 parse
方法来帮咱们解析请求路径,在真实的服务器中传入的第一个参数为 req.url
,第二个参数不传时,query
会被解析成 a=1&b=2
的形式,第二个参数传入 true
,query
属性的查询字符串会被解析成对象的形式。url
url
模块中,将查询字符串 a=1&b=2
转换为对象 { a: '1', b: '2' }
的实现方式实际上是使用正则替换实现的。
模拟查询字符串转换对象的核心逻辑:
1
2
3
4
5
6
7
8
复制代码 |
let str = "a=1&b=2&c=3";
let obj = {};
str.replace(/([^=&]+)=([^=&]+)/g, function() {
obj[arguments[1]] = arguments[2];
});
console.log(obj); // { a: '1', b: '2', c: '3' }
复制代码 |
在上面代码的 replace
方法的回调函数中参数集合的第一项为匹配到的字符串,第二项为第一个分组的值,第三项为第二个分组的值,依次类推,倒数第二项为分组匹配的索引,最后一项为原字符串。
咱们能够经过 req
来获取请求信息,天然也能够经过 res
来设置响应信息返回给客户端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
复制代码 |
const http = require("http");
const server = http.createServer();
server.on("request", function(req, res) {
// 设置响应头(过去的用法),不能屡次调用,见到要认识
res.writeHead(200, { "Content-Type": "text", a: "hello world" });
// 设置响应头(如今的用法,经常使用),能够屡次调用,每次设置一个响应头
res.setHeader("Content-Type", "text");
// 设置状态码,不设置默认为 200
res.statusCode = 200;
// 不发送 Date(日期)响应头
res.sendDate = false;
// 返回内容
res.write("hello world"); // 不会关闭链接
res.end("hello world"); // 将内容返回后关闭链接
});
server.listen(3000, function() {
console.log("server start 3000");
});
复制代码 |
返回给客户端的信息主要分为两部分,分别为响应头和返回给浏览器的内容,在不设置响应头的状况下,默认会设置响应头 Content-Length
和 Date
,表明当前返回给客户端的内容长度和日期。
返回给浏览器的内容能够经过 res
的 write
方法和 end
方法进行发送,write
方法不会断开链接(一般在响应后须要断开与客户端的链接),end
方法会断开链接,在 end
方法存在参数时,会在内部调用 write
将参数内容返回给客户端,并断开链接。
在 net
模块中能够经过 net.createConnection
来建立客户端,并发送请求到服务端,在 http
模块一样能够建立客户端,并向 http
服务器发送请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
复制代码 |
const http = require("http");
// 发送请求的配置
let config = {
host: "localhost",
port: 3000,
method: "get",
headers: {
a: 1
}
};
// 建立客户端
let client = http.request(config, function(res) {
// 接收服务端返回的数据
let arr = [];
res.on("data", function(data) {
arr.push(data);
});
res.on("end", function() {
console.log(Buffer.concat(arr).toString());
});
});
// 发送请求
client.end();
复制代码 |
在 http
模块中经过 request
方法建立客户端,该方法第一个参数为发送请求的配置,包含请求地址、端口号、请求方法以及请求头等,第二个参数为回调函数,在请求被响应后执行,回调函数的参数为服务器的响应对象 res
,建立的客户端经过 end
方法将请求发出与服务端进行通讯。
使用 NodeJS 实现的 “爬虫” 其实就能够经过 http
模块建立的客户端来实现,客户端帮咱们向咱们要抓取数据的地址发送请求,并拿到响应的数据进行解析。
咱们使用本身建立的客户端访问本身的服务端,并体会请求响应的过程,就是用上面 client.js
做为客户端,启动 server.js
后再启动 client.js
查看效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码 |
const http = require("http");
http.createServer(function(req, res) {
console.log("The request came");
// 获取客户端请求信息
console.log(req.method);
console.log(req.headers);
// 返回数据
res.write("hello world");
}).listen(3000, function() {
console.log("server start 3000");
});
复制代码 |
咱们结合 http
模块建立的服务端和客户端实现一个简易版的 “爬虫” 去抓取百度新闻页全部 li
标签内的文章标题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
复制代码 |
const http = require("http");
// 建立服务器
const server = http.createServer();
// 监听请求
server.on("request", function(req, res) {
let client = http.request(
{
host: "news.baidu.com",
method: "get",
port: 80
},
function(r) {
// 接收百度新闻返回的数据
let arr = [];
r.on("data", function(data) {
arr.push(data);
});
r.on("end", function() {
// 处理数据
let result = Buffer.concat(arr).toString();
let matches = result.match(/<li class="bold-item">([\s\S*?])<\/li>/gm);
// 设置返回给浏览器的文档类型和编码格式
res.setHeader("Content-Type", "text/html;charset=utf8");
// 响应浏览器
res.end(matches.join(""));
});
}
);
client.end();
});
server.listen(3000);
复制代码 |
上面的正则匹配中 ([\s\S*?])
表明匹配 <li class="bold-item">
到 <\/li>
之间全部内容(多个字符、非贪婪模式),gm
表明全局并多行匹配。
上面爬取百度新闻数据的过程当中,咱们本身的 Node 服务器扮演了一个 “中间层” 的角色,咱们经过浏览器访问本身的服务器 localhost:3000 触发 request
事件,执行了回调,在回调中建立客户端向 news.baidu.com 发送了请求,并在客户端的回调中处理了响应(百度新闻页返回的数据),将处理后的内容经过咱们本身 Node 服务器的 res
对象返回给了浏览器。
相信在读过本篇文章以后对搭建一个 Node 服务应该已经有了思路,为将来经过 Node 服务实现复杂的业务场景及数据的处理打下了一个基础,但愿初学 Node 的小伙伴在看了这篇文章后能有所收获。