返回文章列表
服务器

Node.js 怎么实现简易静态资源服务器?从基础示例到 serve 功能复刻

匿名
2025-12-11
17小时前
Node.js 怎么实现简易静态资源服务器?从基础示例到 serve 功能复刻

静态资源服务器(HTTP 服务器)可以将静态文件(如 js、css、图片)等通过 HTTP 协议展现给客户端。本文介绍如何基于 Node 实现一个简易的静态资源服务器(类似于 serve)。

基础示例

const fs = require("node:fs");const fsp = require("node:fs/promises");const http = require("node:http");const server = http.createServer(async (req, res) => {  const stat = await fsp.stat("./index.html");  res.setHeader("content-length", stat.size);  fs.createReadStream("./index.html").pipe(res);});server.listen(3000, () => {  console.log("Listening 3000...");});

在上述示例中,我们创建了一个 http server,并以 stream 的方式返回一个 index.html 文件。其中,分别引入了 Node 中的 fs、fsp、http 模块,各个模块的作用如下:

  • fs: 文件系统模块,用于文件读写。
  • fsp: 使用 promise 形式的文件系统模块。
  • http: 用于搭建 HTTP 服务器。

简易 serve 实现

serve 的核心功能非常简单,它以命令行形式指定服务的文件根路径和服务端口,并创建一个 http 服务。

arg - 命令行参数读取

使用 npm 包 arg 读取命令行参数,https://www.npmjs.com/package/arg。

chalk - 控制台信息输出

使用 npm 包 chalk 在控制台输出带有颜色的文字信息,方便调试预览。

源码

// Native - Node built-in moduleconst fs = require("node:fs");const fsp = require("node:fs/promises");const http = require("node:http");const path = require("node:path");// Packagesconst arg = require("arg");const chalk = require("chalk");var config = {entry: "",rewrites: [],redirects: [],etag: false,cleanUrls: false,trailingSlash: false,symlink: false,};/** * eg: --port <string> or --port=<string> * node advance.js -p 3000 | node advance.js --port 3000 */const args = arg({"--port": Number,"--entry": String,"--rewrite": Boolean,"--redirect": Boolean,"--etag": Boolean,"--cleanUrls": Boolean,"--trailingSlash": Boolean,"--symlink": Boolean,});const resourceNotFound = (response, absolutePath) => {  response.statusCode = 404;  fs.createReadStream(path.join("./html", "404.html")).pipe(response);};const processDirectory = async (absolutePath) => {const newAbsolutePath = path.join(absolutePath, "index.html");try {    const newStat = await fsp.lstat(newAbsolutePath);    return [newStat, newAbsolutePath];  } catch (e) {    return [null, newAbsolutePath];  }};/** * @param { http.IncomingMessage } req * @param { http.ServerResponse } res * @param { config } config */const handler = async (request, response, config) => {// 从 request 中获取 pathnameconst pathname = new URL(request.url, `http://${request.headers.host}`)    .pathname;// 获取绝对路径let absolutePath = path.resolve(    config["--entry"] || "",    path.join(".", pathname)  );let statusCode = 200;let fileStat = null;// 获取文件状态try {    fileStat = await fsp.lstat(absolutePath);  } catch (e) {    // console.log(chalk.red(e));  }if (fileStat?.isDirectory()) {    [fileStat, absolutePath] = await processDirectory(absolutePath);  }if (fileStat === null) {    return resourceNotFound(response, absolutePath);  }let headers = {    "Content-Length": fileStat.size,  };  response.writeHead(statusCode, headers);  fs.createReadStream(absolutePath).pipe(response);};const startEndpoint = (port, config) => {const server = http.createServer((request, response) => {    // console.log("request: ", request);    handler(request, response, config);  });  server.on("error", (err) => {    const { code, port } = err;    if (code === "EADDRINUSE") {      console.log(chalk.red(`address already in use [:::${port}]`));      console.log(chalk.green(`Restart server on [:::${port + 1}]`));      startEndpoint(port + 1, entry);      return;    }    process.exit(1);  });  server.listen(port, () => {    console.log(chalk.green(`Open http://localhost:${port}`));  });};startEndpoint(args["--port"] || 3000, args);

扩展

rewrite 和 redirect 的区别?

  • rewrite: 重写,是指服务器在接收到客户端的请求后,返回的是别的文件内容。举个例子,浏览器 A 向服务器请求了一个资源 indexA.html,服务器接收到 indexA.html 请求后,拿 indexB.html 作为实际的响应内容返回给浏览器 A,整个过程对于 A 来说是无感知的。
  • redirect: 重定向,浏览器 A 向服务器请求一个 index.html,服务器告诉浏览器 A “资源不在当前请求路径,需要去通过另外一个地址请求”。浏览器接收到新地址后,重新请求。整个过程中,浏览器需要请求两次。


本文内容仅供参考,不构成任何专业建议。使用本文提供的信息时,请自行判断并承担相应风险。

分享文章
合作伙伴

本站所有广告均是第三方投放,详情请查询本站用户协议