WebFlux support for SSE with multiline fragments
See gh-33194
This commit is contained in:
@@ -41,6 +41,7 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -538,21 +539,25 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport imp
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> format(
|
||||
Flux<DataBuffer> fragmentContent, Fragment fragment, ServerWebExchange exchange) {
|
||||
Flux<DataBuffer> fragmentFlux, Fragment fragment, ServerWebExchange exchange) {
|
||||
|
||||
Charset charset = StandardCharsets.UTF_8;
|
||||
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
|
||||
if (contentType != null && contentType.getCharset() != null) {
|
||||
charset = contentType.getCharset();
|
||||
}
|
||||
MediaType mediaType = exchange.getResponse().getHeaders().getContentType();
|
||||
Charset charset = (mediaType != null && mediaType.getCharset() != null ?
|
||||
mediaType.getCharset() : StandardCharsets.UTF_8);
|
||||
|
||||
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
|
||||
|
||||
String eventLine = fragment.viewName() != null ? "event:" + fragment.viewName() + "\n" : "";
|
||||
String eventLine = (fragment.viewName() != null ? "event:" + fragment.viewName() + "\n" : "");
|
||||
DataBuffer prefix = encodeText(eventLine + "data:", charset, bufferFactory);
|
||||
DataBuffer suffix = encodeText("\n\n", charset, bufferFactory);
|
||||
|
||||
return Flux.concat(Flux.just(prefix), fragmentContent, Flux.just(suffix));
|
||||
Mono<DataBuffer> content = DataBufferUtils.join(fragmentFlux)
|
||||
.map(dataBuffer -> {
|
||||
String s = dataBuffer.toString(charset).replace("\n", "\ndata:");
|
||||
return bufferFactory.wrap(s.getBytes(charset));
|
||||
});
|
||||
|
||||
return Flux.concat(Flux.just(prefix), content, Flux.just(suffix));
|
||||
}
|
||||
|
||||
private DataBuffer encodeText(String text, Charset charset, DataBufferFactory bufferFactory) {
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
import org.springframework.web.reactive.result.view.script.ScriptTemplateConfigurer;
|
||||
import org.springframework.web.reactive.result.view.script.ScriptTemplateViewResolver;
|
||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpResponse;
|
||||
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
@@ -87,7 +88,14 @@ public class FragmentViewResolutionResultHandlerTests {
|
||||
.then(Mono.defer(() -> exchange.getResponse().getBodyAsString()))
|
||||
.block(Duration.ofSeconds(60));
|
||||
|
||||
assertThat(body).isEqualTo("<p>Hello Foo</p><p>Hello Bar</p>");
|
||||
assertThat(exchange.getResponse().getHeaders().getContentType()).isEqualTo(MediaType.TEXT_HTML);
|
||||
assertThat(body).isEqualTo("""
|
||||
<p>
|
||||
Hello Foo
|
||||
</p>\
|
||||
<p>
|
||||
Hello Bar
|
||||
</p>""");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,6 +106,7 @@ public class FragmentViewResolutionResultHandlerTests {
|
||||
.build();
|
||||
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||
MockServerHttpResponse response = exchange.getResponse();
|
||||
|
||||
HandlerResult result = new HandlerResult(
|
||||
new Handler(),
|
||||
@@ -106,15 +115,20 @@ public class FragmentViewResolutionResultHandlerTests {
|
||||
new BindingContext());
|
||||
|
||||
String body = initHandler().handleResult(exchange, result)
|
||||
.then(Mono.defer(() -> exchange.getResponse().getBodyAsString()))
|
||||
.then(Mono.defer(response::getBodyAsString))
|
||||
.block(Duration.ofSeconds(60));
|
||||
|
||||
assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.TEXT_EVENT_STREAM);
|
||||
assertThat(body).isEqualTo("""
|
||||
event:fragment1
|
||||
data:<p>Hello Foo</p>
|
||||
data:<p>
|
||||
data: Hello Foo
|
||||
data:</p>
|
||||
|
||||
event:fragment2
|
||||
data:<p>Hello Bar</p>
|
||||
data:<p>
|
||||
data: Hello Bar
|
||||
data:</p>
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import org.springframework.web.reactive.result.view.script.*
|
||||
|
||||
"""<p>${i18n("hello")} $foo</p>"""
|
||||
"""
|
||||
|<p>
|
||||
| ${i18n("hello")} $foo
|
||||
|</p>""".trimMargin()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import org.springframework.web.reactive.result.view.script.*
|
||||
|
||||
"""<p>${i18n("hello")} $bar</p>"""
|
||||
"""
|
||||
|<p>
|
||||
| ${i18n("hello")} $bar
|
||||
|</p>""".trimMargin()
|
||||
|
||||
Reference in New Issue
Block a user