基本功能 不急著寫下第一行代碼,而是先梳理一下就基本功能而言有哪些步驟。 打開文件待讀取 設置response header 發送文件到客戶端 實現基本功能 代碼結構 創建一個nodejs-static-webserver目錄,在目錄內運行npm init初始化一個package.json文件。 m ...
基本功能
不急著寫下第一行代碼,而是先梳理一下就基本功能而言有哪些步驟。
- 在本地根據指定埠啟動一個http server,等待著來自客戶端的請求
- 當請求抵達時,根據請求的url,以設置的靜態文件目錄為base,映射得到文件位置
- 檢查文件是否存在
- 如果文件不存在,返回404狀態碼,發送not found頁面到客戶端
- 如果文件存在:
- 打開文件待讀取
- 設置response header
- 發送文件到客戶端
- 等待來自客戶端的下一個請求
實現基本功能
代碼結構
創建一個nodejs-static-webserver
目錄,在目錄內運行npm init
初始化一個package.json文件。
mkdir nodejs-static-webserver && cd "$_" // initialize package.json npm init
接著創建如下文件目錄:
-- config
---- default.json
-- static-server.js
-- app.js
default.json
{ "port": 9527, "root": "/Users/sheila1227/Public", "indexPage": "index.html" }
default.js
存放一些預設配置,比如埠號、靜態文件目錄(root)、預設頁(indexPage)等。當這樣的一個請求http://localhost:9527/myfiles/
抵達時. 如果根據root
映射後得到的目錄內有index.html,根據我們的預設配置,就會給客戶端發回index.html的內容。
static-server.js
const http = require('http'); const path = require('path'); const config = require('./config/default'); class StaticServer { constructor() { this.port = config.port; this.root = config.root; this.indexPage = config.indexPage; } start() { http.createServer((req, res) => { const pathName = path.join(this.root, path.normalize(req.url)); res.writeHead(200); res.end(`Requeste path: ${pathName}`); }).listen(this.port, err => { if (err) { console.error(err); console.info('Failed to start server'); } else { console.info(`Server started on port ${this.port}`); } }); } } module.exports = StaticServer;
在這個模塊文件內,我們聲明瞭一個StaticServer
類,並給其定義了start
方法,在該方法體內,創建了一個server
對象,監聽rquest
事件,並將伺服器綁定到配置文件指定的埠。在這個階段,我們對於任何請求都暫時不作區分地簡單地返回請求的文件路徑。path
模塊用來規範化連接和解析路徑,這樣我們就不用特意來處理操作系統間的差異。
app.js
const StaticServer = require('./static-server'); (new StaticServer()).start();
在這個文件內,調用上面的static-server
模塊,並創建一個StaticServer實例,調用其start
方法,啟動了一個靜態資源伺服器。這個文件後面將不需要做其他修改,所有對靜態資源伺服器的完善都發生在static-server.js
內。
在目錄下啟動程式會看到成功啟動的log:
> node app.js
Server started on port 9527
在瀏覽器中訪問,可以看到伺服器將請求路徑直接返回了。
路由處理
之前我們對任何請求都只是向客戶端返迴文件位置而已,現在我們將其替換成返回真正的文件:
routeHandler(pathName, req, res) { } start() { http.createServer((req, res) => { const pathName = path.join(this.root, path.normalize(req.url)); this.routeHandler(pathName, req, res); }).listen(this.port, err => { ... }); }
將由routeHandler
來處理文件發送。
讀取靜態文件
讀取文件之前,用fs.stat
檢測文件是否存在,如果文件不存在,回調函數會接收到錯誤,發送404響應。
respondNotFound(req, res) { res.writeHead(404, { 'Content-Type': 'text/html' }); res.end(`<h1>Not Found</h1><p>The requested URL ${req.url} was not found on this server.</p>`); } respondFile(pathName, req, res) { const readStream = fs.createReadStream(pathName); readStream.pipe(res); } routeHandler(pathName, req, res) { fs.stat(pathName, (err, stat) => { if (!err) { this.respondFile(pathName, req, res); } else { this.respondNotFound(req, res); } }); }
Note:
讀取文件,這裡用的是流的形式
createReadStream
而不是readFile
,是因為後者會在得到完整文件內容之前將其先讀到記憶體里。這樣萬一文件很大,再遇上多個請求同時訪問,readFile
就承受不來了。使用文件可讀流,服務端不用等到數據完全載入到記憶體再發回給客戶端,而是一邊讀一邊發送分塊響應。這時響應里會包含如下響應頭:
Transfer-Encoding:chunked
預設情況下,可讀流結束時,可寫流的
end()
方法會被調用。