Skip to content

Resps ​

Resps 是响应构造工具类:把「正文 + Content-Type(可选下载头)」组装成一个 HttpResponse,控制器只负责把它返回。文本、JSON、HTML、静态文件、图片与 Office 文档导出都用同一套方法,接口不必自己拼响应头。

一、为什么用它 ​

  • 响应头集中设置:Content-Type、Content-Disposition 由工具类按内容类型写好,避免每个接口各写一份,也避免漏写导致浏览器按纯文本渲染。
  • 两种入口:HttpRequest 入口新建一个响应(普通控制器方法);HttpResponse 入口写回当前响应(拦截器、过滤器、SSE、以及已经拿到响应对象的分支)。
  • 扩展名推断 MIME:不确定类型时按文件扩展名推断,推断不出走二进制流,不猜。
  • 下载文件名自动编码:中文名、空格都能正确落到 Content-Disposition。

二、文本与数据响应 ​

java
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。

三、静态文件与错误页 ​

java
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。

java
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 文件名,中文名在各浏览器里都保存为原文:

text
Content-Disposition: attachment; filename="__ 2026.xlsx"; filename*=UTF-8''%E6%8A%A5%E8%A1%A8%202026.xlsx

需要自己拼响应头时,attachmentDisposition(filename) 直接返回这个头的值。

五、文档与图片的导出方法 ​

常用类型各有一个快捷方法,内部就是「固定 Content-Type + 附件下载」,调用方只需要准备字节:

java
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.sheetxlsx 表格导出
excelXls(response, bytes, filename)application/vnd.ms-excelxls 老格式
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 以上的导出:

java
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 请求,服务地址和路径按实际部署替换:

bash
# 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 或文本读取。

MIT Licensed