# Node.js HTTP Module - HTTP 是 Node.js 中的一個 core module - 提供 **HTTP 協定** 相關的 function,以進行 HTTP 傳輸 - 接收 **HTPP Request** (發送 **Response**) - 可直接架設 **HTTP Server** - 接收來自其他程式的 request - 根據 request 的內容,產出對應的 response - 大多用在 server-side 軟體開發 - 發送 **HTTP Request** (接收 **Response**) - 用來向其他程式發出請求 - 可用於爬蟲程式等 client-side 的軟體開發 - 也可用在 server-side 軟體開發 (當資料來自第三方程式時) :::info - 在程式碼中使用 `require()`,引入 HTTP 模組 ```javascript var http = require("http"); ``` ::: ## 關於 HTTP Protocol - HTTP 是 **Hyper Text Transfer Protocol** - 可以用來傳輸文字和檔案的 **傳輸協定** - **傳輸協議** (Protocol) 指的是 **網路傳輸時,使用的規則和標準** - 網路傳輸的過程又被分成[五層](https://ithelp.ithome.com.tw/articles/10236217) (可以想成有五個大步驟),每一層都有各自的、不同的多種協定 - 被應用程式直接使用的協定,是 [應用層協定](https://zh.wikipedia.org/zh-tw/%E5%BA%94%E7%94%A8%E5%B1%82) - 不同的應用場景,會使用不同應用層協定 - 網頁服務: HTTP, HTTPS ... - 檔案傳輸: FTP, SCP - ... ### Client-Server Mode (Model) - HTTP 採用 **Client-Server Mode** - 使用 HTTP 進行傳輸的程式被分成 client 和 server 兩種 - **Client**: 用戶,**使用 server 提供的服務** 的程式 - **Server**: 伺服器,**提供 client 各種服務** 的程式 :::success Client **向 server 發出請求**(request) - 目的可能是: - 要求 server **提供資源** (資料和檔案) - 請 server **執行某些運算** - 提供資料並 **儲存在 server 上** - Server 把處理的結果傳送回來 - 回傳的資料稱為 **回應** (response) ::: :::info Server **接收 client 的請求** - Client 發送的請求中帶有一些資料和參數 - Server 根據 client 提供的資訊,執行不同的功能 - Server 將程式的執行結果放在回應中,並且回傳給 client ::: :::warning **Client 和 Server 是相對的關係** - 一支程式不一定一直都是 client,也不一定一直都是 server - 誰是 client、誰是 server,取決於 **每次資料傳輸的當下**,**兩個程式之間的關係** - 在本次傳輸中,A 要求 B 執行一些任務,那 A 就是 client、B 就是 server - 下一次如果由 B 要求 A 執行,就換成 B 是 client、A 是 server ::: ### HTTP Packet - 每一種傳輸協定,都有一個自己的資料格式 - 在 HTTP 中,一包資料被稱作一個 **HTTP Message** - 不管是 request 還是 response,它們都是用這個格式表示和傳輸 - 其中比較重要的欄位是 header 和 body ![](https://s3.ap-south-1.amazonaws.com/s3.studytonight.com/tutorials/uploads/pictures/1611906247-71449.png) :::success **body** 中的是 **程式本身想要傳輸的資料** - 和 **程式的功能本身相關的資料** - 資料的格式沒有限制 - 例如: 資料庫查詢後的結果、要存在 server 中的資料、網頁、檔案、... - 以寄信來比喻,body 中的資料就是 **信紙上的內容** ::: :::info **Header** 中的是 **傳輸時必須用到的資料,還有跟 Message 本身相關的資料** - 也就是跟程式的 **主要功能無關、但是必要的資料** - 有固定的格式,採用 **key-value** 的形式 - 就像是有多個欄位的表格 - 每個欄位有一個自己的名字 (key) - 每個欄位有一個自己的數值 (value) - 例如: body 使用的資料格式、資料的編碼類型、驗證資訊、Message 的狀態、傳輸的控制信號 ... - 以寄信來比喻,header 中的資料就是 **信封上的內容** ::: ## 用 Node.js 建立 HTTP Server (Web Server) `http` 中的其中一個 class - `Server`,可以快速建立一個 HTTP Server - 建立 `Server` 物件 - 設計處理 request 的 function (request listener) - 當接收到 request 時,這個 function 就會被呼叫 - 設定監聽的 port number - 啟動 `Server` 物件 :::info 以下是一個基本的寫法 ```javascript // 載入 http 模組 var http = require("http"); // 建立 Server 物件 var server = http.createServer(function (req, res) { // 在這個 function 中處理 request // 產生 response 的內容並發送回去 }); // 啟動 server 並讓它監聽 port 3000 server.listen(3000); ``` ::: 在 `server.listen()` 中,可以加入一個 callback function,作為 Listening Listener - 當 server 成功啟動時,這個 function 會被呼叫 :::success 可以透過這個 listener 顯示一些文字,讓我們知道 server 被正常啟動 ```javascript // 載入 http 模組 var http = require("http"); // 建立 Server 物件 var server = http.createServer(function (req, res) { // 在這個 function 中處理 request }); // 啟動 server 並讓它監聽 port 3000 server.listen(3000, function () { console.log("Server, 啟動!"); // 啟動成功就會顯示這段文字 }); ``` ::: 以上的兩個 listener 也可以用其他的方式表示,只要符合 JS 中 function 的語法就行 :::success 使用箭頭函式改寫 ```javascript var http = require("http"); // 建立 Server 物件 var server = http.createServer((req, res) => { // ... }); // 啟動 server 並讓它監聽 port 3000 server.listen(3000, () => { console.log("Server, 啟動!"); }); ``` ::: 關於 `Server` 的完整內容,可以參考 [官方文件](https://nodejs.org/api/http.html#class-httpserver) ### Request Listener - Request Listener 本身是一個 function - 當 server 物件收到 request 就會呼叫這個 function - 會傳入兩個參數,依序代表: - **request**: 其中包含從 client 端發送來的資料、client 資訊等 - **response**: http 模組幫我們建立的物件,呼叫這個物件的 method,可以設定並會傳資料給 client - 通常使用參數名稱 `req` 代表 request 物件,`res` 代表 response 物件 - 所以 request listener 通常會是下面的格式 ```javascript function (req, res) { // 處理 request } ``` :::warning - Server 物件傳進來的物件,第一個一定是 request,第二個一定是 response - 跟 function 參數的名稱沒有關係 - 這兩個參數的名字沒有限定,在語法層面說是可以任意命名 ```javascript function (a, b) { // 要叫做 a, b 也不違反語法 } ``` - 但是在實務上,參數命名會和它的實際意義有關聯 - 避免造成混淆 - 增加程式碼的可讀性、可維護性 ::: 在這個 function 中,要包含所有處理 request 所需的工作 - 可以輸出文字訊息,用來確認真的有收到 request :::success - 在瀏覽器中打開 http://localhost:3000 ,可以發送簡單的 request 給 server 物件 - 但因為我們的程式並沒有執行任何回應,所以瀏覽器會保持在載入中的狀態 - 每次打開 URL,程式就會輸出一次 "收到 request!" ```javascript var http = require("http"); // 建立 Server 物件 var server = http.createServer((req, res) => { // 每次收到 request 就會輸出下面訊息 console.log("收到 request!"); }); // 啟動 server 並讓它監聽 port 3000 server.listen(3000, () => { // 只有 server 啟動時會執行 console.log("Server, 啟動!"); }); ``` ::: ### 產生 HTTP Response - 呼叫 response 的 method,設定 response 中的內容,並把它發送回 client - 常用的 method 有 - `res.end()`: 結束編輯 response,並發送給 client - `res.write()`: 設定 HTTP body,也就是要發送給 client 的資料 - `res.writeHead()`: 設定 HTTP header,用來設定 response 的狀態或其他額外資訊 完整使用方式,可以參考 [官方文件](https://nodejs.org/api/http.html#class-httpserverresponse) #### `res.end()` 對 `res` 呼叫 `end()`,回傳 response 給 client :::success ```javascript var http = require("http"); var server = http.createServer((req, res) => { console.log("收到 request!"); // 回應 client res.end(); }); server.listen(3000, () => { console.log("Server, 啟動!"); }); ``` - 打開 http://localhost:3000 ,會看到空白的網頁,因為程式中沒有回應任何資料 ::: 可以在 `end()` 加上要給 client 的資料 - 資料通常是文字格式 - 一個 response 只能執行一次 `end()`,所以只用 `end()` 的話,要一次設定好所有要發送的資料 :::success ```javascript var http = require("http"); var server = http.createServer((req, res) => { console.log("收到 request!"); // 發送 Hello, World! 給 client res.end("Hello, World!"); }); server.listen(3000, () => { console.log("Server, 啟動!"); }); ``` - 打開 http://localhost:3000 ,會看到回應的文字 ::: `http` 模組會自動判斷文字內容的格式,幫我們設定對應的 http header - 可以直接發送 HTML 字串 :::success ```javascript var http = require("http"); var server = http.createServer((req, res) => { console.log("收到 request!"); // 發送 Hello, World! // 模組會判斷這是一段 HTML // 自動補齊其他必要的文字 res.end("<h1>Hello, World!</h1>"); }); server.listen(3000, () => { console.log("Server, 啟動!"); }); ``` - 打開 http://localhost:3000 ,會看到回應的文字以網頁的形式顯示 ::: #### `res.write()` - 寫入資料到 response 中 - 跟 `end()` 不同的是,`write()` 可以呼叫多次,所以可以分段設定要回應的資料 - `write()` 會把這次的資料,接在目前所有的資料之後 :::success ```javascript var http = require("http"); var server = http.createServer((req, res) => { console.log("收到 request!"); // 設定 response 的內容 res.write("<h1>Hello, World!</h1>"); // 先寫入一行 res.write("<p>Welcome to GDSC @ NTCU</p>"); // 再寫入一行 // 送出 response res.end(); }); server.listen(3000, () => { console.log("Server, 啟動!"); }); ``` - 打開 http://localhost:3000 ,可以看到寫入的兩行內容都出現在網頁上 ::: `write()` 和 `end()` 可以結合使用 - `end()` 也會把資料寫在最後面,但是一寫入就會送出回應 - 所以 `end()` 要擺在最後 :::success ```javascript var http = require("http"); var server = http.createServer((req, res) => { console.log("收到 request!"); // 先寫入一行 res.write("<h1>Hello, World!</h1>"); // 寫入後馬上送出 res.end("<p>Welcome to GDSC @ NTCU</p>"); }); server.listen(3000, () => { console.log("Server, 啟動!"); }); ``` - 結果和上一個例子相同 ::: #### `res.writeHead()` 用來設定 response 的 [HTTP header](https://zh.wikipedia.org/zh-tw/HTTP%E5%A4%B4%E5%AD%97%E6%AE%B5) - Header 中包含 response 的相關資訊 - 例如: 狀態、內容的類型、編碼等等 只有一個參數時,此參數表示 HTTP Status - 常用的 status 和代表的意義 - 200 - OK: 執行成功 - 404 - Not found: 請求的內容不存在 - 500 - Server side error: 伺服器發生錯誤 :::success ```javascript var http = require("http"); var server = http.createServer((req, res) => { console.log("收到 request!"); // 通常用 200 表示運作正常 res.writeHead(200); // 先寫入一行 res.write("<h1>Hello, World!</h1>"); // 寫入後馬上送出 res.end("<p>Welcome to GDSC @ NTCU</p>"); }); server.listen(3000, () => { console.log("Server, 啟動!"); }); ``` ::: 有兩個參數時,第二個參數用來表示其他的 header :::success ```javascript var http = require("http"); var server = http.createServer((req, res) => { console.log("收到 request!"); // 設定 Status 和 Content-Type 兩個欄位 res.writeHead(200, { "Content-Type": "text/html" }); // 先寫入一行 res.write("<h1>Hello, World!</h1>"); // 寫入後馬上送出 res.end("<p>Welcome to GDSC @ NTCU</p>"); }); server.listen(3000, () => { console.log("Server, 啟動!"); }); ``` ::: ### 讀取 HTTP Request - Request 物件中包含從 client 發送過來的許多資料 - Header: 包含 request 的相關資料、client 的相關資料,常見的有 - request 的 path - 使用的 HTTP method、protocol - Body: 包含使用者要傳送給 server 的資料 #### `req.url` - `req.url` 是 request 物件的一個 property - 是一筆資料,不可執行 - 可以直接讀取並使用 - 代表的是使用者發送 request 時,使用的 URL 中的 path 區段 ![](https://learn.microsoft.com/en-us/clarity/media/path-segments.png) - 根據不同的 `req.url`,執行不同的程式來產生 response,就可以做到不同網址產生不同網頁的效果 :::success ```javascript var http = require("http"); var server = http.createServer((req, res) => { console.log(req.url); res.write("<h1>Hello!</h1>"); // 根據不同 url 回應不同內容 switch (req.url) { case "/": res.end("<p>We are GDSC @ NTCU!</p>"); break; case "/home": res.end("<p>This is home page~</p>"); break; case "/student": res.end("<p>Welcome to NTCU!</p>"); break; default: res.end("Sorry! Page not found..."); } }); server.listen(3000, () => { console.log("Server, 啟動!\n"); }); ``` 用瀏覽器開啟不同的 URL,觀察產生的效果 - http://localhost:3000 - http://localhost:3000/home - http://localhost:3000/student - http://localhost:3000/其他字串 :::