本文介绍如何通过 Vert.x 的路由机制为 StaticHandler 全局设置 Content-Disposition: attachment 响应头,使所有静态资源(包括图片、PDF、DOCX 等)均以下载方式提供,而非在浏览器中直接渲染。
本文介绍如何通过 vert.x 的路由机制为 statichandler 全局设置 `content-disposition: attachment` 响应头,使所有静态资源(包括图片、pdf、docx 等)均以下载方式提供,而非在浏览器中直接渲染。
默认情况下,Vert.x 的 StaticHandler 会根据文件 MIME 类型自动设置 Content-Type,并由浏览器决定是内嵌显示(如 image/png、application/pdf)还是触发下载(如 application/vnd.openxmlformats-officedocument.wordprocessingml.document)。但若需统一强制下载所有文件,不能仅依赖 MIME 类型映射——因为 StaticHandler 本身不提供 setContentDisposition() 这类配置方法。
解决方案是在路由链中插入一个前置中间件,在响应即将写入前动态注入 Content-Disposition: attachment 头。关键在于利用 HttpServerResponse.headersEndHandler():该回调在响应头已生成、但尚未发送给客户端时触发,是修改响应头的最后安全时机。
以下是推荐实现方式:
public class Server extends AbstractVerticle {
@Override
public void start() throws Exception {
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
// 为 /static/* 路由添加响应头拦截器
router.route("/static/*")
.handler(rc -> {
HttpServerResponse response = rc.response();
// 在响应头即将发送前注入 Content-Disposition
response.headersEndHandler(v -> {
// 可选:检查状态码避免对错误响应误设(如 404)
if (response.getStatusCode() == 200) {
response.putHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment");
}
});
rc.next(); // 继续执行后续处理器(即 StaticHandler)
})
.handler(StaticHandler.create("data"));
vertx.createHttpServer().requestHandler(router).listen(8080);
}
}⚠️ 注意事项:
- 顺序至关重要:headersEndHandler 必须在 StaticHandler 执行前注册,否则响应头可能已被冻结;
- 避免重复设置:headersEndHandler 是事件驱动的,每个请求仅触发一次,不会重复添加头;
- 状态码判断建议:如示例所示,建议校验 response.getStatusCode() == 200,防止对 404、500 等错误响应也添加下载头;
-
文件名保留:Content-Disposition: attachment 默认不指定 filename=,浏览器将使用 URL 路径末尾作为下载文件名。如需自定义名称,可结合 rc.request().path() 提取原始文件名并构造完整头值,例如:
response.putHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
此方案无需修改 StaticHandler 源码或重写其逻辑,完全基于 Vert.x 的响应生命周期钩子,轻量、可靠且符合响应式编程范式。

















