静态文件
serveStatic 将本地目录挂载为可公开访问的静态资源路径,支持浏览器缓存协议(ETag / Last-Modified)和 HTTP Range 请求(视频流、大文件断点续传)。
快速开始
cangjie
import ace_web.*
import ace_web.static_file.*
let app = App()
// 将 ./public 目录挂载到 /static 前缀
app.use(serveStatic("./public", "/static"))访问 GET /static/logo.png 将映射到磁盘 ./public/logo.png。
参数说明
cangjie
func serveStatic(
dir : String, // 本地目录路径(相对或绝对)
prefix : String = "/", // URL 前缀,默认挂载到根路径
options: StaticOptions = StaticOptions()
): MiddlewareStaticOptions 配置项:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
index | String | "index.html" | 目录访问时的默认文件 |
etag | Bool | true | 是否生成并校验 ETag |
lastModified | Bool | true | 是否输出 Last-Modified 头 |
maxAgeMs | Int64 | 0 | Cache-Control: max-age 秒数(毫秒转换) |
dotfiles | String | "ignore" | 点文件处理:"ignore" / "allow" / "deny" |
spaFallback | Bool | false | 开启 SPA 回退,404 时返回 index.html |
声明式注册(@Middleware)
在框架宏层可用 @Middleware 将静态文件中间件纳入 IoC 管理:
cangjie
import ace_framework.*
import ace_web.static_file.*
@Middleware[order: 5]
class StaticFilesMiddleware <: MiddlewareProvider {
@Value["static.dir"]
var dir: String = "./public"
@Value["static.prefix"]
var prefix: String = "/assets"
public func middleware(): Middleware {
serveStatic(dir, prefix, StaticOptions(maxAgeMs: 86400_000))
}
}order 值越小越先执行。静态文件中间件建议排在鉴权中间件之前,避免不必要的鉴权消耗。
Range 请求(断点续传 / 视频流)
serveStatic 自动处理 Range: bytes=start-end 请求,无需额外配置:
GET /assets/video.mp4 HTTP/1.1
Range: bytes=0-1048575响应:
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1048575/52428800
Content-Length: 1048576
Accept-Ranges: bytes视频播放
HTML5 <video> 标签依赖 Range 请求实现拖动进度条。确保静态文件中间件在路由之前注册,或使用独立端口/路径前缀避免被其他中间件拦截。
缓存头
- ETag:基于文件内容哈希,客户端再次请求时携带
If-None-Match,内容未变返回304 Not Modified。 - Last-Modified:基于文件修改时间,配合
If-Modified-Since头使用。 - Cache-Control:通过
maxAgeMs控制客户端强缓存时长。
cangjie
// 静态资源缓存 7 天
serveStatic("./dist", "/", StaticOptions(maxAgeMs: 7 * 86400_000))开发环境禁用缓存
开发时建议将 maxAgeMs 设为 0,避免修改后浏览器仍读缓存。可通过配置文件区分环境:
toml
# config/dev.toml
[static]
maxAgeMs = 0
# config/prod.toml
[static]
maxAgeMs = 604800000 # 7 天SPA 回退(单页应用)
前端 SPA(React/Vue 等)使用 History 路由时,刷新页面会请求服务端不存在的路径,此时需将请求回退到 index.html:
cangjie
app.use(serveStatic(
"./dist",
"/",
StaticOptions(
spaFallback: true,
maxAgeMs: 86400_000
)
))开启 spaFallback 后,所有未命中静态文件的 GET 请求(且非 API 路径)均返回 ./dist/index.html。
API 路径冲突
若 API 与前端资源共用同一端口,请确保 API 路由在静态文件中间件之前注册,否则 API 路径可能被 SPA 回退拦截,返回 index.html 而非 JSON 响应。
cangjie
app.use(apiRouter.routes()) // 先注册 API 路由
app.use(serveStatic("./dist", "/", StaticOptions(spaFallback: true))) // 再注册静态完整示例
cangjie
import ace_web.*
import ace_web.static_file.*
import ace_router.*
import ace_http.*
main(): Unit {
let app = App()
let api = Router("/api")
api.get("/health") { ctx =>
ctx.json("""{"status":"ok"}""")
}
// API 优先
app.use(api.routes())
// 静态文件(含 SPA 回退)
app.use(serveStatic(
"./dist",
"/",
StaticOptions(spaFallback: true, maxAgeMs: 86400_000)
))
listen(app, "0.0.0.0", 8080)
}