我正在为请求处理程序编写一种包装程序,以使它们流式传输HTTP响应。我现在所拥有的是
处理程序响应包装:
@Component
public class ResponseBodyEmitterProcessor {
public ResponseBodyEmitter process(Supplier<?> supplier) {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
Executors.newSingleThreadExecutor()
.execute(() -> {
CompletableFuture<?> future = CompletableFuture.supplyAsync(supplier)
.thenAccept(result -> {
try {
emitter.send(result, MediaType.APPLICATION_JSON_UTF8);
emitter.complete();
} catch (IOException e) {
emitter.completeWithError(e);
}
});
while (!future.isDone()) {
try {
emitter.send("", MediaType.APPLICATION_JSON_UTF8);
Thread.sleep(500);
} catch (IOException | InterruptedException e) {
emitter.completeWithError(e);
}
}
});
return emitter;
}
}
控制器:
@RestController
@RequestMapping("/something")
public class MyController extends AbstractController {
@GetMapping(value = "/anything")
public ResponseEntity<ResponseBodyEmitter> getAnything() {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(process(() -> {
//long operation
}));
}
我正在做的只是每半秒发送一次空字符串以保持请求有效。某些工具需要不通过超时将其关闭。这里的问题是我在响应中看不到任何 Content-Type 标头。根本没有任何东西,尽管我从控制器方法中返回ResponseEntity,正如在该线程中所说的:
https://github.com/spring-projects/spring-framework/issues/18518
看起来只有TEXT_HTML媒体类型可以接受流式传输。根本没有流json的方法吗?我什至使用objectMapper手动将dtos映射到json字符串,但是仍然没有运气。尝试过APPLICATION_JSON和APPLICATION_STREAM_JSON-不起作用。在不同的浏览器中尝试过-结果相同。
我还手动在控制器中为ResponseEntity设置Content-Type标头。现在响应中有标题,但是我不确定数据是否真的在流化。在Chrome中,我只能看到我的操作结果,而看不到我正在发送的中间字符(将它们更改为“ a”进行测试)。
我检查了两个选项的请求处理时间:
据我了解,等待状态的意思是:“等待第一个字节出现”。似乎与发射器一样,第一个字节出现得更早-这看起来像我需要的。我可以认为这是我的解决方案有效的证明吗?
也许在春季还有另一种方法吗?我所需要的只是通过向浏览器发送一些无用的数据来通知浏览器该请求仍在处理中,直到完成实际操作为止-然后返回结果。
任何帮助将不胜感激。
答案 0 :(得分:0)
查看ResponseBodyEmitter#send
的来源,似乎应该已经在MediaType
方法中设置了指定的AbstractHttpMessageConverter#addDefaultHeaders
,但前提是还没有其他contentType头。
protected void addDefaultHeaders(HttpHeaders headers, T t, MediaType contentType) throws IOException{
if (headers.getContentType() == null) {
MediaType contentTypeToUse = contentType;
// ...
if (contentTypeToUse != null) {
headers.setContentType(contentTypeToUse);
}
}
// ...
}
我建议在此处设置一个断点,看看为什么不应用标题。也许@RestController
设置了默认标头。
作为解决方法,您可以尝试通过MVC控制器中的注释设置contentType标头。
例如
@RequestMapping(value = "/something", produces = MediaType.APPLICATION_JSON_UTF8)