Spring模拟分块返回端点:如何让浏览器实时接收分块内容?
问题描述
我们团队遇到一个问题:某个GET端点在特定参数下会分块返回超大JSON,例如1000行的JSON会分批返回,导致应用因超时无法完整获取数据。为调试该问题,我们需要模拟该端点的行为。目前已实现一个模拟端点,生成随机字符串并分块写入OutputStream后刷新,但浏览器和POSTMAN均在所有内容返回后才显示,而非实时接收分块。请问如何修改才能实现实时分块显示?
当前实现代码
private static final int readBufSize = 10; private static final int generatedStringSize = readBufSize * 10000; @GetMapping(path = "/v2/payload/mocklargepayload") public void simulateLargePayload(HttpServletResponse response){ try(InputStream inputStream = IOUtils.toInputStream(RandomStringUtils.randomAlphanumeric(generatedStringSize)); OutputStream outputStream = response.getOutputStream()) { final byte[] buffer = new byte[readBufSize]; for(int i = 0; i < generatedStringSize; i+= readBufSize){ inputStream.read(buffer, 0, readBufSize - 1); buffer[buffer.length - 1] = '\n'; log.info("Read bytes: {}", buffer); outputStream.write(buffer); log.info("Wrote bytes {}", buffer); Thread.sleep(500); log.info("Flushing stream"); outputStream.flush(); } } catch (IOException | InterruptedException e) { log.error("Received exception: {}", e.getClass().getSimpleName()); } }
解决方案
要让浏览器和Postman实时接收分块内容,需正确配置HTTP分块传输编码并规避容器层缓冲,具体修改点如下:
启用分块传输编码
在方法开头添加响应头配置,明确告知客户端采用分块传输:response.setContentType("text/plain"); // 根据实际场景调整,比如模拟JSON用application/json response.setHeader("Transfer-Encoding", "chunked"); response.setBufferSize(readBufSize); // 强制容器缓冲区大小与分块一致,避免缓冲注意:不要设置
Content-Length头,分块传输不需要该字段,否则容器会自动缓冲所有内容直到生成完毕。修复数据流读取逻辑
原代码中inputStream.read()无法保证每次读取指定长度的字节,需处理实际读取的字节数:int bytesRead = inputStream.read(buffer, 0, readBufSize - 1); if (bytesRead == -1) { break; // 数据流结束,退出循环 } buffer[bytesRead] = '\n'; // 在实际读取的字节后添加换行,而非固定位置 outputStream.write(buffer, 0, bytesRead + 1); // 只写入有效字节规避容器额外缓冲
部分Spring Boot容器(如Tomcat)默认存在响应缓冲,response.setBufferSize(readBufSize)可强制容器不缓冲超过指定大小的内容,配合flush()操作就能实时输出分块。适配测试工具特性
- Postman:切换到“Stream”模式查看响应,关闭可能导致缓冲的自动重定向等设置;
- 浏览器:部分浏览器会缓冲文本内容直到换行或达到一定大小,添加
\n分隔块是合理做法,若模拟JSON需保证分块是合法的JSON片段或整体结构的一部分。
修改后的完整代码
private static final int readBufSize = 10; private static final int generatedStringSize = readBufSize * 10000; @GetMapping(path = "/v2/payload/mocklargepayload") public void simulateLargePayload(HttpServletResponse response){ try { // 配置分块传输响应头 response.setContentType("text/plain"); response.setHeader("Transfer-Encoding", "chunked"); response.setBufferSize(readBufSize); try(InputStream inputStream = IOUtils.toInputStream(RandomStringUtils.randomAlphanumeric(generatedStringSize)); OutputStream outputStream = response.getOutputStream()) { final byte[] buffer = new byte[readBufSize]; for(int i = 0; i < generatedStringSize; i+= readBufSize){ int bytesRead = inputStream.read(buffer, 0, readBufSize - 1); if (bytesRead == -1) { break; } buffer[bytesRead] = '\n'; log.info("Read bytes: {}", new String(buffer, 0, bytesRead + 1)); outputStream.write(buffer, 0, bytesRead + 1); log.info("Wrote bytes {}", new String(buffer, 0, bytesRead + 1)); Thread.sleep(500); log.info("Flushing stream"); outputStream.flush(); } } } catch (IOException | InterruptedException e) { log.error("Received exception: {}", e.getClass().getSimpleName()); } }
内容的提问来源于stack exchange,提问作者Jason
相关产品推荐
相关产品推荐

