node静态资源服务器
三天不喝水 人气:0前言
静态资源服务器(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 读取命令行参数,www.npmjs.com/package/arg。
chalk - 控制台信息输出
使用 npm 包 chalk 在控制台输出带有颜色的文字信息,方便调试预览。
源码
// Native - Node built-in module const fs = require("node:fs"); const fsp = require("node:fs/promises"); const http = require("node:http"); const path = require("node:path"); // Packages const 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 中获取 pathname const 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 “资源不在当前请求路径,需要去通过另外一个地址请求”。浏览器接收到新地址后,重新请求。整个过程中,浏览器需要请求两次。
加载全部内容