无框架的 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: "application/javascript",
  css: "text/css",
  png: "image/png",
  jpg: "image/jpg",
  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 }。如果可以提供文件服务(服务器进程具有访问权限且未发现路径遍历漏洞),我们将返回 HTTP 状态码 200 作为 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);