静态文件服务

在 Web 应用中,我们通常需要提供静态资源,如 HTML、CSS、图片、客户端 JavaScript 文件等。Express 提供了一个内置中间件 express.static,用于轻松托管静态文件。本章将详细介绍其用法、配置和最佳实践。

1. 什么是静态文件服务?

静态文件是指内容不会动态改变的文件,比如样式表(CSS)、图像(JPEG、PNG)、前端脚本(JS)等。当客户端请求这些资源时,服务器只需读取文件并返回其内容,无需任何业务逻辑处理。提供静态文件服务是 Web 服务器的基本功能之一。

2. express.static 的基本用法

express.static 是 Express 唯一保留的内置中间件,它基于 serve-static 模块,负责托管静态资源。使用时,只需将包含静态资源的目录名称传递给该中间件。

假设你的项目结构如下:

my-express-app/
├── public/
│   ├── css/
│   │   └── style.css
│   ├── images/
│   │   └── logo.png
│   └── js/
│       └── main.js
└── app.js

app.js 中添加:

const express = require('express');
const app = express();

app.use(express.static('public'));

app.listen(3000, () => console.log('Server running on port 3000'));

现在,你可以通过以下 URL 访问这些文件:

  • http://localhost:3000/css/style.css
  • http://localhost:3000/images/logo.png
  • http://localhost:3000/js/main.js

注意,URL 中不需要包含 public 目录名,因为 express.staticpublic 目录映射到了根路径。

3. 多个静态目录

如果你有多个静态资源目录,可以多次调用 express.static。Express 会按照中间件的顺序依次查找文件,一旦找到匹配的文件就停止搜索。

app.use(express.static('public'));
app.use(express.static('assets'));

当请求 /images/logo.png 时,Express 会先在 public 目录中查找,如果找不到,再到 assets 目录中查找。这种机制允许你为不同来源的静态资源设置优先级。

4. 虚拟路径前缀

有时我们希望静态文件不要直接挂载在根路径下,而是有一个前缀,例如 /static。这可以通过给 express.static 指定挂载路径来实现:

app.use('/static', express.static('public'));

现在,文件的访问 URL 变为:

  • http://localhost:3000/static/css/style.css
  • http://localhost:3000/static/images/logo.png

虚拟路径有助于组织路由,避免与动态路由冲突。

5. 使用绝对路径

在上面的例子中,'public' 是相对路径,相对于启动 Node.js 进程的工作目录。为了更可靠,建议使用绝对路径,结合 path 模块:

const path = require('path');
app.use('/static', express.static(path.join(__dirname, 'public')));

这样无论从哪个目录启动应用,都能正确找到 public 文件夹。

6. 配置选项

express.static 接受一个可选的对象作为第二个参数,用于自定义行为。常用选项:

  • maxAge:设置缓存控制头的 max-age(单位毫秒)。例如 maxAge: '7d'maxAge: 1000 * 60 * 60 * 24 * 7
  • dotfiles:是否允许提供点文件(如 .htaccess),可选 'allow''deny''ignore'(默认)。
  • etag:是否生成 ETag 头(默认 true)。
  • lastModified:是否设置 Last-Modified 头(默认 true)。
  • setHeaders:自定义响应头的函数。

示例:

app.use('/static', express.static('public', {
  maxAge: '1d', // 客户端缓存一天
  setHeaders: (res, path, stat) => {
    if (path.endsWith('.html')) {
      res.setHeader('Cache-Control', 'no-cache');
    }
  }
}));

7. 安全考虑

express.static 内置了安全机制,防止目录遍历攻击(如请求 /static/../app.js)。它会自动解析路径并确保文件位于指定的根目录内。因此,你无需额外担心路径遍历漏洞。

但是,仍需注意以下几点:

  • 不要将包含敏感信息的文件(如配置文件、源代码)放在公共静态目录中。
  • 对于生产环境,建议使用反向代理(如 Nginx、Apache)来托管静态文件,它们性能更好且更安全。

8. 示例:完整的静态文件服务

下面是一个完整的 Express 应用,演示了静态文件服务与动态路由的共存:

const express = require('express');
const path = require('path');
const app = express();

// 托管静态文件,并设置缓存
app.use('/static', express.static(path.join(__dirname, 'public'), {
  maxAge: '7d',
  immutable: true // 适用于 hash 命名的文件
}));

// 动态路由
app.get('/', (req, res) => {
  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <link rel="stylesheet" href="/static/css/style.css">
      </head>
      <body>
        <h1>Hello World</h1>
        <img src="/static/images/logo.png" alt="Logo">
        <script src="/static/js/main.js"></script>
      </body>
    </html>
  `);
});

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

在此例中,动态路由 / 返回的 HTML 页面中引用了静态资源,这些资源由 /static 前缀提供。

9. 最佳实践

  • 使用绝对路径:避免工作目录变化导致文件找不到。
  • 合理设置缓存:为不经常变动的资源设置较长的 maxAge,减少服务器负载。
  • 结合版本控制:对静态文件使用哈希命名(如 main.a1b2c3.js),并设置 immutable: true,实现永久缓存。
  • 在生产环境使用反向代理:Nginx 处理静态文件比 Node.js 更高效,可以减轻应用服务器的压力。
  • 分离静态资源和动态路由:通过虚拟路径前缀(如 /static)清晰区分。
总结: express.static 是 Express 提供的一个简单而强大的中间件,能够快速搭建静态文件服务。通过合理配置和遵循最佳实践,你可以在开发和生产环境中高效地托管静态资源。