1. 程式人生 > >極簡 Node.js 入門 - 5.1 建立 HTTP 伺服器

極簡 Node.js 入門 - 5.1 建立 HTTP 伺服器

> 極簡 Node.js 入門系列教程:[https://www.yuque.com/sunluyong/node](https://www.yuque.com/sunluyong/node) > > 本文更佳閱讀體驗:[https://www.yuque.com/sunluyong/node/http-server](https://www.yuque.com/sunluyong/node/http-server) 使用 Node.js 建立 http 伺服器需要使用內建的 `http` 模組 ## 建立 web server Node.js 是執行在伺服器環境的 JavaScript,這裡的伺服器更多指的是物理概念的伺服器,也就是主機。使用 Node.js 建立 HTTP 伺服器指的是軟體概念的伺服器,也就是 web server,類似於 nginx、apache ```javascript const http = require('http'); const server = http.createServer((req, res) => { res.write('Hello\n'); res.end(); }); server.listen(9527, () => { console.log('Web Server started at port 9527'); }); ``` 上面 10 行程式碼建立了一個最簡單的 HTTP 伺服器,伺服器監聽埠號 9527,接收到請求後返回字串 `Hello\n` ,可以使用瀏覽器或者 curl 工具測試
![image.png](https://cdn.nlark.com/yuque/0/2020/png/87727/1589614191989-98fedc1a-2bf0-41ea-9b54-1ec3197d0676.png#align=left&display=inline&height=76&margin=%5Bobject%20Object%5D&name=image.png&originHeight=152&originWidth=626&size=56636&status=done&style=none&width=313)
createServer 的回撥函式在接收到請求後被呼叫 ### req req 代表本次 http request,是一個可讀流,常用有幾個屬性 - url:本地請求的地址 - method:HTTP 請求的方法(GET、POST、DELETE、PUT 等) - headers::請求的 HTTP header ### res res 代表本次http response,是一個可寫流,常用的屬性方法有 - writeHead(statusCode,[, StatusMessage[, headers]]):傳送響應首部,包含狀態碼、狀態資訊、響應頭 - write(chunk):向響應主體中寫入字串或者 buffer - end(chunk):向伺服器發出訊號,可以攜帶最後傳送的資料,表明已傳送所有響應頭和主體,每個響應都需要呼叫一次 - getHeader(name):返回指定 name 的 header - getHeaders():返回包含了所有 header 資訊的物件 - setHeader(name, value):設定響應頭,和 writeHead() 合併,有衝突時優先使用 writeHead() - statusCode:設定響應 HTTP status ## 返回請求資訊的 web server 上面例子中所有請求返回的結果都一樣,可以對請求識別,做一些差異化的處理,下面例子展示瞭如何把每次請求的基本資訊返回 ```javascript const http = require('http'); const server = http.createServer((req, res) =>
{ const { url, method, headers } = req; res.setHeader('content-type', 'text/html'); res.write(`請求 URL: ${url}\n`); res.write(`請求方法: ${method}\n`); res.write(`請求 headers:${JSON.stringify(headers, null, ' ')}`); res.end('\n'); }); server.listen(9527, () => { console.log('Web Server started at port 9527'); }); ``` 使用 curl 或者瀏覽器測試
![image.png](https://cdn.nlark.com/yuque/0/2020/png/87727/1589616586491-bd2f7c0f-0bc9-4e83-bd3e-364c41a8ee0e.png#align=left&display=inline&height=159&margin=%5Bobject%20Object%5D&name=image.png&originHeight=318&originWidth=504&size=83278&status=done&style=none&width=252) ## 返回檔案內容 上面例子和真實的 web server 還有很大差距,下面例子展示了一個最簡單的返回檔案內容的靜態資源伺服器 ```javascript const http = require('http'); const path = require('path'); const fs = require('fs'); const mime = require('mime-types'); // 靜態資源根目錄,可以設定為本地的任意有許可權目錄,放入 a.jpg 測試 const ROOT_DIRECTORY = '/public'; const server = http.createServer((req, res) => { const { url } = req; const filePath = path.join(ROOT_DIRECTORY, url); fs.readFile(filePath, (err, chunk) => { if (err) { res.writeHead(404, { 'content-type': 'text/html', }); res.end('檔案不存在!'); } else { res.writeHead(200, { 'content-type': mime.contentType(path.extname(url)), }); res.end(chunk); } }); }); server.listen(9527, () => { console.log('Web Server started at port 9527'); }); ``` > demo 中使用了 mime-types 包來根據檔名稱獲取檔案的 Content-Type,執行 demo 需要在程式碼目錄安裝 mime-types  > tnpm i -S mime-types [HTTP 狀態碼](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status) 1. 200 OK,表示請求正常處理 1. 404 Not Found,表示請求資源在伺服器不存在
在測試目錄下放入圖片 `a.jpg` 使用瀏覽器測試 `127.0.0.1:9527/a.jpg` 
![image.png](https://cdn.nlark.com/yuque/0/2020/png/87727/1589617839623-5d3434c8-4658-4a16-a239-2add7ff0a92f.png#align=left&display=inline&height=203&margin=%5Bobject%20Object%5D&name=image.png&originHeight=293&originWidth=483&size=76667&status=done&style=none&width=334) ## 讀取電影檔案 理論上讀取電影檔案可以使用和上面一樣的程式碼,但實際執行會發現電影檔案在完全讀取到記憶體後才返回給瀏覽器,這樣返回內容耗時極長,而且電影檔案過大的話程式也沒有辦法處理,HTTP 協議是支援分段傳輸的(Transfer-Encoding: chunked),既然 res 是可寫流,可以簡單使用 stream 來做到邊讀取內容邊返回給瀏覽器,而不是一次讀取完成後返回 ```javascript const http = require('http'); const path = require('path'); const fs = require('fs'); const mime = require('mime-types'); // 靜態資源根目錄 const ROOT_DIRECTORY = '/Users/undefined/node-demo/public'; const server = http.createServer((req, res) => { const { url } = req; const filePath = path.join(ROOT_DIRECTORY, url); fs.access(filePath, fs.constants.R_OK, err => { if (err) { res.writeHead(404, { 'content-type': 'text/html', }); res.end('檔案不存在!'); } else { res.writeHead(200, { 'content-type': mime.contentType(path.extname(url)), }); fs.createReadStream(filePath).pipe(res); } }); }); server.listen(9527, () => { console.log('Web Server started at port 9527'); }); ``` 使用 stream 章節介紹的 fs.createReadStream() 和 pipe() 可以輕鬆將檔案匯入 http response

Node.js 官網一次 [HTTP 傳輸解析](https://nodejs.org/zh-cn/docs/guides/anatomy-of-an-http-transaction/)對 HTTP Server 做了入門講解,順便介紹了一些 HTTP 協議的相關知識,值