Resps
Resps 是响应构造工具类:把「正文 + Content-Type(可选下载头)」组装成一个 HttpResponse,控制器只负责把它返回。文本、JSON、HTML、静态文件、图片与 Office 文档导出都用同一套方法,接口不必自己拼响应头。
一、为什么用它
- 响应头集中设置:
Content-Type、Content-Disposition由工具类按内容类型写好,避免每个接口各写一份,也避免漏写导致浏览器按纯文本渲染。 - 两种入口:
HttpRequest入口新建一个响应(普通控制器方法);HttpResponse入口写回当前响应(拦截器、过滤器、SSE、以及已经拿到响应对象的分支)。 - 扩展名推断 MIME:不确定类型时按文件扩展名推断,推断不出走二进制流,不猜。
- 下载文件名自动编码:中文名、空格都能正确落到
Content-Disposition。
二、文本与数据响应
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.Resps;
import nexus.io.model.body.RespBodyVo;
public class TextController {
public HttpResponse html(HttpRequest request) {
return Resps.html(request, "<h1>tio-boot</h1>");
}
public HttpResponse json(HttpRequest request) {
return Resps.json(request, RespBodyVo.ok("hello"));
}
public HttpResponse txt(HttpRequest request) {
return Resps.txt(request, "plain text");
}
public HttpResponse xml(HttpResponse response) {
return Resps.xml(response, "<root/>");
}
}| 方法 | Content-Type | 说明 |
|---|---|---|
html(request/response, body) | text/html | 页面片段或整页 |
json(request/response, body) | application/json | 对象自动序列化,String 与基本类型原样输出 |
txt(request/response, body) | text/plain | 纯文本,text(...) 是 txt 的别名 |
xml(response, body) | application/xml | 站点地图、订阅源等 |
svg(response, body) | image/svg+xml | 内联矢量图 |
js(request, body) | application/javascript | 前端脚本 |
css(request, body) | text/css | 样式表 |
string(..., charset, mimeTypeStr) | 自定义 | 需要别的类型时用它 |
带 charset 参数的重载用显式字符集替换请求默认值;string(...) 是最底层的一个,其余方法都只是给它配上固定的 MIME。
三、静态文件与错误页
import java.io.File;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.Resps;
public class FileController {
public HttpResponse download(HttpRequest request) throws Exception {
return Resps.file(request, new File("files/report.pdf"));
}
public HttpResponse notFound(HttpRequest request) throws Exception {
return Resps.resp404(request);
}
}| 方法 | 行为 |
|---|---|
file(request, File/path) | 读文件并返回;按扩展名给 Content-Type;带 Last-Modified |
try304(request, lastModified) | 命中协商缓存时返回 304,未命中返回 null 交给调用方继续 |
bytes(request/response, bytes, extension) | 已有字节、按扩展名给类型 |
resp404(...) / resp500(...) | 使用配置的错误页;没有配置页时退回内置页面 |
forward(request, path) | 服务端转发到新路径 |
fail(response, text) / error(response, text) | 分别返回 400 与 500,正文为给定文本 |
四、二进制内容与下载
不确定类型时用 bytesWithContentType;要作为附件下载时用 download,它一次性设置类型与 Content-Disposition。
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.Resps;
public class AttachController {
public HttpResponse attach(byte[] bytes) {
HttpResponse response = TioRequestContext.getResponse();
// 通用写法:类型与文件名都由调用方给出
return Resps.download(response, bytes, "application/octet-stream", "report-2026-10-03.bin");
}
public HttpResponse cover(byte[] bytes) {
HttpResponse response = TioRequestContext.getResponse();
// 图片:类型由扩展名推断
return Resps.image(response, bytes, "png");
}
}contentDisposition(response, filename) 只追加下载头,适合已经自己设好类型的场景。它同时写出 ASCII 备用名和 UTF-8 文件名,中文名在各浏览器里都保存为原文:
Content-Disposition: attachment; filename="__ 2026.xlsx"; filename*=UTF-8''%E6%8A%A5%E8%A1%A8%202026.xlsx需要自己拼响应头时,attachmentDisposition(filename) 直接返回这个头的值。
五、文档与图片的导出方法
常用类型各有一个快捷方法,内部就是「固定 Content-Type + 附件下载」,调用方只需要准备字节:
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.Resps;
public class ExportController {
public HttpResponse excel(byte[] xlsx) {
return Resps.excel(TioRequestContext.getResponse(), xlsx, "对话日志-2026-10-03.xlsx");
}
public HttpResponse pdf(byte[] pdf) {
return Resps.pdf(TioRequestContext.getResponse(), pdf, "report.pdf");
}
public HttpResponse word(byte[] docx) {
return Resps.docx(TioRequestContext.getResponse(), docx, "report.docx");
}
public HttpResponse slide(byte[] pptx) {
return Resps.pptx(TioRequestContext.getResponse(), pptx, "report.pptx");
}
}| 方法 | Content-Type | 典型用途 |
|---|---|---|
excel(response, bytes[, filename]) | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | xlsx 表格导出 |
excelXls(response, bytes, filename) | application/vnd.ms-excel | xls 老格式 |
pdf(response, bytes[, filename]) | application/pdf | 报表、票据 |
docx(response, bytes[, filename]) | application/vnd.openxmlformats-officedocument.wordprocessingml.document | 文档导出 |
doc(response, bytes[, filename]) | application/msword | 老格式文档 |
pptx(response, bytes[, filename]) | application/vnd.openxmlformats-officedocument.presentationml.presentation | 幻灯片导出 |
ppt(response, bytes[, filename]) | application/vnd.ms-powerpoint | 老格式幻灯片 |
image(response, bytes, extension[, filename]) | 按扩展名(png、jpg、gif 等) | 图片预览或图片下载 |
imagePng(response, bytes) / imageJpeg(response, bytes) | image/png / image/jpeg | 验证码、二维码这类固定格式 |
download(response, bytes, contentType, filename) | 任意 | 其他类型 |
类型常量(CONTENT_TYPE_EXCEL、CONTENT_TYPE_EXCEL_XLS、CONTENT_TYPE_DOCX、CONTENT_TYPE_PPTX)是公开字段,需要自己拼响应头或做二次封装时可以直接引用。
生成 xlsx 等文件内容属于业务侧的事:表格导出可以配合 api-table 的写入器,见EasyExcel 导出;Resps 只负责把字节和响应头送出去。
六、大文件:直接发送磁盘文件
上面每个导出方法都有接收 File 的版本,文件不读进内存,由传输层零拷贝发送,适合几十 MB 以上的导出:
import java.io.File;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.Resps;
public class LargeExportController {
public HttpResponse excel(File xlsx) {
return Resps.excel(TioRequestContext.getResponse(), xlsx, "订单-2026-10-31.xlsx");
}
public HttpResponse any(File file) {
// Content-Type 按扩展名推断
return Resps.download(TioRequestContext.getResponse(), file, "合同.pdf");
}
}可用的方法有 excel、excelXls、pdf、docx、doc、pptx、ppt 以及 download(response, file[, contentType], filename)。文件不存在时返回 404;filename 为空时使用磁盘文件名;响应附带 X-Accel-Buffering: no,放在 nginx 后面时不会被缓冲到临时文件。完整示例、发送过程和导出文件的清理方式见大文件导出:零拷贝发送 Excel、Word、PDF、PPT。
七、方法一览
| 分类 | 方法 |
|---|---|
| 文本 | html、json、txt、text、xml、svg、js、css、string |
| 文件 | file、try304、bytes |
| 二进制 | bytesWithContentType、bytesWithHeaders、imagePng、imageJpeg、image、download、contentDisposition、attachmentDisposition |
| 文档导出 | excel、excelXls、pdf、docx、doc、pptx、ppt(均支持 byte[] 与 File) |
| 重定向 | redirect、redirectForever、redirectWithPage、forward |
| 错误 | fail、error、resp404、resp500 |
| 辅助 | getMimeTypeStr、contentTypeOf、disableProxyBuffering |
大多数文本与二进制方法同时提供 HttpRequest 与 HttpResponse 两个入口;文档导出与图片这几组方法以 HttpResponse 为入口,写回传入的对象并把它返回,配合 TioRequestContext.getResponse() 使用。file、css、js、redirect 走请求入口,svg、xml、fail、error 走响应入口。
八、使用要点
- 文件名带日期:同一份报表多次导出时,在文件名里带上日期或时间,避免浏览器把多次下载当成同名文件覆盖;后端只负责编码与发送。
- 响应按请求协商压缩:客户端用
Accept-Encoding声明可接受的响应编码。只有允许 gzip 时,框架才尝试压缩适合的正文;identity或gzip;q=0不会触发自动 gzip。命令行客户端可用curl --compressed同时声明压缩能力并自动解压。 - 大文件用文件路径:导出文件已经在磁盘上时,用
excel(response, file, filename)等File版本零拷贝发送,不要先读进字节数组;静态文件用file(...),try304配合Last-Modified能省掉重复传输。 - 错误响应也有类型:
fail/error会带 400 / 500 状态码,正文是纯文本,调用方可以直接展示。
九、响应压缩协商
Gzip 可减少 JSON、HTML、CSS 等重复文本的传输量,降低带宽占用。压缩需要计算开销,因此自动压缩仅处理至少 300 字节的正文,而且只在压缩后更小时采用结果;图片、音频、视频和显式跳过压缩的响应不会进入自动压缩流程。
请求头 Accept-Encoding 声明的是客户端接收响应的能力,不表示请求正文已经压缩。自动协商规则如下:
| 请求头 | 自动 gzip 行为 |
|---|---|
Accept-Encoding: gzip | 允许尝试压缩 |
Accept-Encoding: br, gzip;q=0.5 | 允许尝试 gzip 压缩 |
Accept-Encoding: identity | 不压缩 |
Accept-Encoding: gzip;q=0 | 不压缩 |
Accept-Encoding: *;q=1 | 允许尝试 gzip 压缩 |
Accept-Encoding: gzip;q=0, *;q=1 | 显式禁止 gzip,不压缩 |
| 未发送该头或头值为空 | 保守返回未压缩正文 |
参与协商的响应携带 Vary: Accept-Encoding,并保留已有的 Origin 等 Vary 项,使缓存区分不同编码能力的请求。仅在实际采用 gzip 时才设置 Content-Encoding: gzip;允许压缩不代表每个响应都一定会压缩。setIsSupportGzip(false) 可禁用请求对应的自动压缩;设置为 true 仍须通过请求头协商,不会强制向不支持 gzip 的客户端发送压缩正文。
以下示例对通用接口发送 JSON POST 请求,服务地址和路径按实际部署替换:
# Request an identity response.
curl -i -H 'Accept-Encoding: identity' -H 'Content-Type: application/json' --data '{}' http://localhost:8080/api/example
# Negotiate compression and let curl decode the response body.
curl --compressed -i -H 'Content-Type: application/json' --data '{}' http://localhost:8080/api/example
# Explicitly reject gzip, even when other encodings are allowed.
curl -i -H 'Accept-Encoding: gzip;q=0, *;q=1' -H 'Content-Type: application/json' --data '{}' http://localhost:8080/api/example浏览器通常自动完成协商与解压。自行实现 HTTP 客户端时,只有能正确解压 gzip 才应声明支持它,并根据响应的 Content-Encoding 决定是否解压;不要把压缩字节直接当作 JSON 或文本读取。
