1. 程式人生 > >詳解NodeJS搭建HTTP伺服器的實現步驟

詳解NodeJS搭建HTTP伺服器的實現步驟

前言

在 NodeJS 中用來建立服務的模組是 http 核心模組,本篇就來介紹關於使用 http 模組搭建 HTTP 伺服器和客戶端的方法,以及模組的基本 API。

HTTP 伺服器

1、建立 HTTP 伺服器

在 NodeJS 中,建立 HTTP 伺服器可以與 net 模組建立 TCP 伺服器對比,建立伺服器有也兩種方式。

方式 1:

const http = require("http");
 
const server = http.createServer(function(req, res) {
  // ......
});
 
server.listen(3000);

方式 2:

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 中依然存在。

const http = require("http");
 
const server = http.createServer();
 
// net 模組事件
server.on("connection", function(socket) {
  console.log("連線成功");
});
 
server.listen(3000);

2、獲取請求資訊

在請求物件 req 中存在請求的方法、請求的 url(包含引數,即查詢字串)、當前的 HTTP 協議版本和請求頭等資訊。

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 進行解析。

const url = require("url");
let str = "http://user:[email protected]: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:[email protected]:8080/src/index.html?a=1&b=2#hash' }

在被解析路徑返回的物件中有幾個屬性被經常使用:

  • host:主機(域名 + 埠號);
  • hostname:主機名;
  • query:請求引數(查詢字串或引數物件);
  • pathname:資源路徑(根據不同的路徑返回不同的資源)。

我們使用 url 的 parse 方法來幫我們解析請求路徑,在真實的伺服器中傳入的第一個引數為 req.url,第二個引數不傳時,query 會被解析成 a=1&b=2 的形式,第二個引數傳入 true,query 屬性的查詢字串會被解析成物件的形式。

url 模組中,將查詢字串 a=1&b=2 轉換為物件 { a: ‘1’, b: ‘2’ } 的實現方式其實是使用正則替換實現的。

模擬查詢字串轉換物件的核心邏輯:

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 方法的回撥函式中引數集合的第一項為匹配到的字串,第二項為第一個分組的值,第三項為第二個分組的值,依次類推,倒數第二項為分組匹配的索引,最後一項為原字串。

3、設定響應資訊

我們可以通過 req 來獲取請求資訊,自然也可以通過 res 來設定響應資訊返回給客戶端。

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 將引數內容返回給客戶端,並斷開連線。

HTTP 客戶端

在 net 模組中可以通過 net.createConnection 來建立客戶端,併發送請求到服務端,在 http 模組同樣可以建立客戶端,並向 http 伺服器傳送請求。

// 客戶端:client.js
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 模組建立的客戶端來實現,客戶端幫我們向我們要抓取資料的地址傳送請求,並拿到響應的資料進行解析。

同時使用 HTTP 客戶端和伺服器

我們使用自己建立的客戶端訪問自己的服務端,並體會請求響應的過程,就是用上面 client.js 作為客戶端,啟動 server.js 後再啟動 client.js 檢視效果。

// 伺服器:server.js
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 標籤內的文章標題。

// 簡易爬蟲:crawl.js
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> 之間所有內容(多個字元、非貪婪模式),gm 代表全域性並多行匹配。
  • 上面爬取百度新聞資料的過程中,我們自己的 Node 伺服器扮演了一個 “中間層” 的角色,我們通過瀏覽器訪問自己的伺服器 localhost:3000 觸發 request 事件,執行了回撥,在回撥中建立客戶端向 news.baidu.com 傳送了請求,並在客戶端的回撥中處理了響應(百度新聞頁返回的資料),將處理後的內容通過我們自己 Node 伺服器的 res 物件返回給了瀏覽器。

    最後

    為了幫助大家讓學習變得輕鬆、高效,給大家免費分享一大批資料,幫助大家在成為全棧工程師,乃至架構師的路上披荊斬棘。在這裡給大家推薦一個前端全棧學習交流圈:866109386.歡迎大家進群交流討論,學習交流,共同進步。

    當真正開始學習的時候難免不知道從哪入手,導致效率低下影響繼續學習的信心。

    但最重要的是不知道哪些技術需要重點掌握,學習時頻繁踩坑,最終浪費大量時間,所以有有效資源還是很有必要的。

    最後祝福所有遇到瓶疾且不知道怎麼辦的前端程式設計師們,祝福大家在往後的工作與面試中一切順利。
    在這裡插入圖片描述