From b5b115e52c02bd3b3ec97e5602ee49375a9b78f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Fri, 7 Apr 2023 11:25:47 +0200 Subject: [PATCH] Fix SSE with indenting serializer in WebMvc.fn This commit ensures that HTTP headers like "text/event-stream" are correctly forwarded to the converter used in SseServerResponse for proper pretty print handling. Close gh-30277 --- .../servlet/function/SseServerResponse.java | 14 +++++++--- .../function/SseServerResponseTests.java | 28 +++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/SseServerResponse.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/SseServerResponse.java index 773c8fdbff83..5bb7d569a6e5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/SseServerResponse.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/SseServerResponse.java @@ -49,6 +49,7 @@ * Server-Sent Events. * * @author Arjen Poutsma + * @author Sebastien Deleuze * @since 5.3.2 */ final class SseServerResponse extends AbstractServerResponse { @@ -91,7 +92,7 @@ protected ModelAndView writeToInternal(HttpServletRequest request, HttpServletRe } DefaultAsyncServerResponse.writeAsync(request, response, result); - this.sseConsumer.accept(new DefaultSseBuilder(response, context, result)); + this.sseConsumer.accept(new DefaultSseBuilder(response, context, result, this.headers())); return null; } @@ -114,15 +115,19 @@ private static final class DefaultSseBuilder implements SseBuilder { private final List> messageConverters; + private final HttpHeaders httpHeaders; + private final StringBuilder builder = new StringBuilder(); private boolean sendFailed; - public DefaultSseBuilder(HttpServletResponse response, Context context, DeferredResult deferredResult) { + public DefaultSseBuilder(HttpServletResponse response, Context context, DeferredResult deferredResult, + HttpHeaders httpHeaders) { this.outputMessage = new ServletServerHttpResponse(response); this.deferredResult = deferredResult; this.messageConverters = context.messageConverters(); + this.httpHeaders = httpHeaders; } @Override @@ -207,7 +212,7 @@ private void writeObject(Object data) throws IOException { for (HttpMessageConverter converter : this.messageConverters) { if (converter.canWrite(dataClass, MediaType.APPLICATION_JSON)) { HttpMessageConverter objectConverter = (HttpMessageConverter) converter; - ServerHttpResponse response = new MutableHeadersServerHttpResponse(this.outputMessage); + ServerHttpResponse response = new MutableHeadersServerHttpResponse(this.outputMessage, this.httpHeaders); objectConverter.write(data, MediaType.APPLICATION_JSON, response); this.outputMessage.getBody().write(NL_NL); this.outputMessage.flush(); @@ -277,9 +282,10 @@ private static final class MutableHeadersServerHttpResponse extends DelegatingSe private final HttpHeaders mutableHeaders = new HttpHeaders(); - public MutableHeadersServerHttpResponse(ServerHttpResponse delegate) { + public MutableHeadersServerHttpResponse(ServerHttpResponse delegate, HttpHeaders headers) { super(delegate); this.mutableHeaders.putAll(delegate.getHeaders()); + this.mutableHeaders.putAll(headers); } @Override diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java index 250c8141b64f..c89172ee5950 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java @@ -33,6 +33,7 @@ /** * @author Arjen Poutsma + * @author Sebastien Deleuze */ class SseServerResponseTests { @@ -89,6 +90,33 @@ void sendObject() throws Exception { assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected); } + @Test + void sendObjectWithPrettyPrint() throws Exception { + Person person = new Person("John Doe", 42); + ServerResponse response = ServerResponse.sse(sse -> { + try { + sse.send(person); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); + + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setPrettyPrint(true); + ServerResponse.Context context = () -> Collections.singletonList(converter); + + ModelAndView mav = response.writeTo(this.mockRequest, this.mockResponse, context); + assertThat(mav).isNull(); + + String expected = "data:{\n" + + "data: \"name\" : \"John Doe\",\n" + + "data: \"age\" : 42\n" + + "data:}\n" + + "\n"; + assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected); + } + @Test void builder() throws Exception { ServerResponse response = ServerResponse.sse(sse -> {