無框架的 Node.js 伺服器

本文展示了一個使用 Node.js 構建的靜態檔案伺服器,未使用任何框架。Node.js 目前的狀態是,幾乎所有我們需要的靜態檔案伺服器功能都由內建 API 和少量程式碼提供。

示例

使用 Node.js 構建的靜態檔案伺服器

js
import * as fs from "node:fs";
import * as http from "node:http";
import * as path from "node:path";

const PORT = 8000;

const MIME_TYPES = {
  default: "application/octet-stream",
  html: "text/html; charset=UTF-8",
  js: "text/javascript",
  css: "text/css",
  png: "image/png",
  jpg: "image/jpeg",
  gif: "image/gif",
  ico: "image/x-icon",
  svg: "image/svg+xml",
};

const STATIC_PATH = path.join(process.cwd(), "./static");

const toBool = [() => true, () => false];

const prepareFile = async (url) => {
  const paths = [STATIC_PATH, url];
  if (url.endsWith("/")) paths.push("index.html");
  const filePath = path.join(...paths);
  const pathTraversal = !filePath.startsWith(STATIC_PATH);
  const exists = await fs.promises.access(filePath).then(...toBool);
  const found = !pathTraversal && exists;
  const streamPath = found ? filePath : `${STATIC_PATH}/404.html`;
  const ext = path.extname(streamPath).substring(1).toLowerCase();
  const stream = fs.createReadStream(streamPath);
  return { found, ext, stream };
};

http
  .createServer(async (req, res) => {
    const file = await prepareFile(req.url);
    const statusCode = file.found ? 200 : 404;
    const mimeType = MIME_TYPES[file.ext] || MIME_TYPES.default;
    res.writeHead(statusCode, { "Content-Type": mimeType });
    file.stream.pipe(res);
    console.log(`${req.method} ${req.url} ${statusCode}`);
  })
  .listen(PORT);

console.log(`Server running at http://127.0.0.1:${PORT}/`);

分解

以下程式碼行匯入了 Node.js 的內部模組。

js
import * as fs from "node:fs";
import * as http from "node:http";
import * as path from "node:path";

接下來是用於建立伺服器的函式。https.createServer 返回一個 Server 物件,我們可以透過監聽 PORT 來啟動它。

js
http
  .createServer((req, res) => {
    /* handle http requests */
  })
  .listen(PORT);

console.log(`Server running at http://127.0.0.1:${PORT}/`);

非同步函式 prepareFile 返回以下結構:{ found: boolean, ext: string, stream: ReadableStream }。如果檔案可以被服務(伺服器程序有訪問許可權且未發現路徑遍歷漏洞),我們將返回 200 的 HTTP 狀態碼作為 statusCode,表示成功(否則返回 HTTP 404)。請注意,其他狀態碼可以在 http.STATUS_CODES 中找到。對於 404 狀態,我們將返回 '/404.html' 檔案的內容。

請求檔案的副檔名將被解析並轉換為小寫。然後,我們將搜尋 MIME_TYPES 集合以查詢正確的 MIME 型別。如果未找到匹配項,我們將使用 application/octet-stream 作為預設型別。

最後,如果沒有錯誤,我們將傳送請求的檔案。file.stream 將包含一個 Readable 流,該流將被管道傳輸到 resWritable 流的一個例項)。

js
res.writeHead(statusCode, { "Content-Type": mimeType });
file.stream.pipe(res);