在 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 的小夥伴在看了這篇文章後能有所收穫。