静态文件服务器
本指南介绍在 AdonisJS 应用中提供静态文件。你将学习如何:
- 安装并配置静态文件中间件
- 理解何时使用静态文件而非编译后的资源
- 为最佳性能配置缓存、ETag 和 HTTP 请求头
- 为安全性控制对点文件的访问
- 为特定文件类型设置自定义请求头
- 将静态文件复制到生产构建中
概述
静态文件服务器让你可以直接从文件系统提供文件,而无需为每个文件创建路由处理器。这对于不需要处理的资源至关重要,如 favicon、robots.txt 文件、用户上传内容或可下载的 PDF。
如果没有静态文件服务器,你需要为想要提供的每个文件创建单独的路由。这会迅速变得难以维护:
// 没有静态中间件 —— 繁琐且容易出错
router.get('/favicon.ico', async ({ response }) => {
return response.download('public/favicon.ico')
})
router.get('/robots.txt', async ({ response }) => {
return response.download('public/robots.txt')
})
router.get('/images/logo.png', async ({ response }) => {
return response.download('public/images/logo.png')
})
// ... 可能有数百个路由
有了静态中间件,public 目录中的所有文件都会自动可用。该中间件在你的路由被访问之前拦截 HTTP 请求。如果存在与请求路径匹配的文件,它会使用适当的 HTTP 请求头(用于缓存和性能)提供该文件。如果文件不存在,请求会像往常一样继续到你的路由处理器。
AdonisJS 静态文件服务器在开发期间很方便,但在生产环境中,你应该优先通过反向代理(Nginx、Caddy、Traefik、Apache)或 CDN 来提供静态文件。这些工具是专为静态文件交付而构建的,比 Node.js 进程提供更好的性能、缓存和压缩。这让你的 AdonisJS 服务器可以专注于处理动态请求。
有关推荐的生产环境设置,请参阅 部署指南 。
AdonisJS 中的关键区别:public 目录中的文件按原样提供,不进行任何处理,而 resources 目录中的文件由你的资源打包器(如 Vite)处理。将已经是最终形式的文件放在 public 中。
安装
@adonisjs/static 包已通过 web 起步套件预配置。如果你使用的是不同的起步套件,可以手动安装和配置它。
使用以下命令安装并配置该包:
node ace add @adonisjs/static
查看 add 命令执行的步骤
-
使用检测到的包管理器安装
@adonisjs/static包。 -
在
adonisrc.ts文件中注册以下服务提供者。
{
providers: [
// ...其他 providers
() => import('@adonisjs/static/static_provider')
]
}
-
创建
config/static.ts文件。 -
在
start/kernel.ts文件中注册以下中间件。
server.use([
() => import('@adonisjs/static/static_middleware')
])
配置
静态中间件的配置存储在 config/static.ts 文件中。
import { defineConfig } from '@adonisjs/static'
const staticServerConfig = defineConfig({
enabled: true,
etag: true,
lastModified: true,
dotFiles: 'ignore',
})
export default staticServerConfig
enabled
enabled 属性允许你在不将中间件从中间件栈移除的情况下临时禁用它。这在调试或测试不同配置时很有用。将其设置为 false 以停止提供静态文件,同时保持中间件已注册。
{
enabled: true
}
etag
etag 属性控制服务器是否为缓存验证生成
ETag
。ETag 帮助浏览器确定其缓存的文件版本是否仍然有效,而无需再次下载。
当浏览器请求一个它已缓存的文件时,它会发送 ETag 值。如果文件没有更改,服务器会以 304 Not Modified 状态响应,节省带宽。默认启用此选项,并且通常应在生产环境中保持启用。
{
etag: true
}
lastModified
lastModified 属性启用
Last-Modified
请求头。服务器使用文件系统中文件的修改时间(
stat.mtime
属性)作为请求头值。
浏览器可以将此请求头与 ETag 一起用于缓存验证。与 ETag 一样,默认启用此选项。
{
lastModified: true
}
dotFiles
dotFiles 属性定义如何处理以圆点开头的文件请求(如 .env 或 .gitignore)。你可以设置三个值之一:'ignore'(默认)、'deny' 或 'allow'。
'ignore' 选项假装点文件不存在,并返回 404 状态码。出于安全考虑,这是推荐的设置。'deny' 选项使用 403 状态码显式拒绝访问。'allow' 选项像处理任何其他文件一样提供点文件。
{
dotFiles: 'ignore' // 推荐
}
将 dotFiles 设置为 'allow' 可能会在 .env 或 .git 目录被意外放入 public 文件夹时暴露敏感文件。推荐使用 'ignore' 设置(默认)。它返回 404 响应,就像文件不存在一样,防止信息泄露。
如果你需要为域名验证提供特定文件(如用于 SSL 证书的 .well-known/acme-challenge),请创建一个不带前导点的子目录,并将你的验证工具配置为使用该路径。
acceptRanges
acceptRanges 属性允许浏览器恢复中断的下载,而不是从头开始。启用后,服务器会向响应添加 Accept-Ranges 请求头。这对于视频或软件下载等大文件特别有用。该属性默认为 true。
{
acceptRanges: true
}
cacheControl
cacheControl 属性启用
Cache-Control
请求头。此请求头告诉浏览器和 CDN 在检查更新之前应缓存文件多长时间。启用后,你可以使用 maxAge 和 immutable 属性来微调缓存行为。
{
cacheControl: true
}
maxAge
maxAge 属性为 Cache-Control 请求头设置
max-age
指令。这告诉浏览器在检查更新之前可以缓存文件多长时间。你可以以毫秒数指定值,或使用时间表达式字符串如 '30 mins'、'1 day' 或 '1 year'。
{
cacheControl: true,
maxAge: '30 days'
}
immutable
immutable 属性向 Cache-Control 请求头添加
immutable
指令。这告诉浏览器该文件在其缓存生命周期内永远不会更改,从而允许更激进的缓存。
将此用于带有版本化或哈希文件名的文件(如 app-v2.css 或 bundle-abc123.js)。默认情况下,immutable 是禁用的。
{
cacheControl: true,
maxAge: '1 year',
immutable: true
}
immutable 指令仅在同时设置了 maxAge 时才有效。如果你启用了 immutable 而没有设置 maxAge,浏览器会忽略它。这防止了在没有明确过期时间的情况下的意外长期缓存。
::::
headers
headers 属性接受一个函数,该函数为特定文件返回自定义 HTTP 请求头。该函数接收文件路径作为第一个参数,
文件状态
对象作为第二个参数。这允许你根据文件类型、大小或其他属性设置不同的请求头。
该函数应返回一个对象,其中键是请求头名称,值是请求头值。如果函数返回 undefined 或不返回任何内容,则不会为该文件添加额外的请求头。
{
headers: (path, stats) => {
/**
* 为 .mc2 文件设置自定义内容类型
* 因为默认情况下它们无法被识别
*/
if (path.endsWith('.mc2')) {
return {
'content-type': 'application/octet-stream'
}
}
/**
* 为 HTML 文件添加安全请求头
* 以防止 XSS 攻击
*/
if (path.endsWith('.html')) {
return {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY'
}
}
}
}
提供静态文件
一旦注册了中间件,你就可以在 public 目录中创建文件,并使用其文件路径在浏览器中访问它们。例如,./public/css/style.css 文件可以在 http://localhost:3333/css/style.css 访问。
以下是生产环境中典型的 public 目录的样子:
public/
├── favicon.ico # 浏览器标签页图标
├── robots.txt # 搜索引擎爬取指令
├── sitemap.xml # 面向搜索引擎的 SEO 站点地图
├── images/
│ ├── logo.png # 静态徽标(不需要优化)
│ └── og-image.jpg # 社交媒体预览图
├── downloads/
│ ├── user-guide.pdf # 可下载文档
│ └── terms.pdf # 法律文档
└── uploads/
└── avatars/ # 用户上传的头像
这些文件中的每一个都可以在其对应的 URL 访问:
http://localhost:3333/favicon.icohttp://localhost:3333/images/logo.pnghttp://localhost:3333/downloads/user-guide.pdf
public 目录与 resources 目录
理解何时使用 public 目录与 resources 目录,对于正确组织应用的资源至关重要。
对于已经是最终形式且不需要任何处理的文件,请使用 public 目录:
- Favicon 和应用图标
robots.txt和sitemap.xml文件- 不需要优化的静态图片(徽标、图标)
- 可下载文件(PDF、ZIP 压缩包、可执行文件)
- 你想按原样提供的第三方 JavaScript 库
- 用户上传的内容(头像、文档)
对于需要编译或优化的文件,请配合使用资源打包器的 resources 目录:
- 需要编译的 CSS/SCSS 文件
- 需要转译的现代 JavaScript/TypeScript
- 受益于优化和响应式变体的图片
- 需要版本控制或缓存清除哈希的资源
- 任何应该由 Vite 或你的构建管道处理的文件
public/styles/main.scss # 这不会被编译
public/app.ts # 这不会被转译
resources/css/main.scss # 由 Vite 编译
resources/js/app.ts # 由 Vite 转译
一个常见的错误是将源文件(如 .scss 或现代 .ts)放在 public 目录中,并期望它们被编译。静态中间件会原样提供文件,而不进行任何处理。需要编译的源文件应该放在 resources 目录中,并由 Vite 或你的资源打包器处理。
将静态文件复制到生产环境
当你运行 node ace build 命令时,public 目录中的文件会自动复制到 build 文件夹。这确保了你的静态文件在生产环境中与编译后的应用代码一同可用。
复制 public 文件的规则在 adonisrc.ts 文件中定义:
{
metaFiles: [
{
pattern: 'public/**',
reloadServer: false
}
]
}
pattern 属性使用 glob 语法来匹配 public 目录中的所有文件。reloadServer: false 设置表示在开发期间对这些文件的更改不需要重启开发服务器。
如果你在开发服务器运行时向 public 目录添加文件,你无需重启。静态中间件会立即提供它们。但是,如果你修改了 config/static.ts 文件,你需要重启服务器以使配置更改生效。