極簡 Node.js 入門系列教程:https://www.yuque.com/sunluyong/node
使用 Node.js 創建 http 服務器需要使用內置的 http
模塊
創建 web server
Node.js 是運行在服務器環境的 JavaScript,這里的服務器更多指的是物理概念的服務器,也就是主機。使用 Node.js 創建 HTTP 服務器指的是軟件概念的服務器,也就是 web server,類似於 nginx、apache
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 工具測試
createServer 的回調函數在接收到請求后被調用
req
req 代表本次 http request,是一個可讀流,常用有幾個屬性
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
上面例子中所有請求返回的結果都一樣,可以對請求識別,做一些差異化的處理,下面例子展示了如何把每次請求的基本信息返回
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');
});
返回文件內容
上面例子和真實的 web server 還有很大差距,下面例子展示了一個最簡單的返回文件內容的靜態資源服務器
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
- 200 OK,表示請求正常處理
- 404 Not Found,表示請求資源在服務器不存在
在測試目錄下放入圖片 a.jpg
使用瀏覽器測試 127.0.0.1:9527/a.jpg
讀取電影文件
理論上讀取電影文件可以使用和上面一樣的代碼,但實際執行會發現電影文件在完全讀取到內存后才返回給瀏覽器,這樣返回內容耗時極長,而且電影文件過大的話程序也沒有辦法處理,HTTP 協議是支持分段傳輸的(Transfer-Encoding: chunked),既然 res 是可寫流,可以簡單使用 stream 來做到邊讀取內容邊返回給瀏覽器,而不是一次讀取完成后返回
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 傳輸解析對 HTTP Server 做了入門講解,順便介紹了一些 HTTP 協議的相關知識,值得閱讀