owned this note
owned this note
Published
Linked with GitHub
# 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

:::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 區段

- 根據不同的 `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/其他字串
:::