16
16
17
17
package org .springframework .web .servlet .mvc .method .annotation ;
18
18
19
- import java .io .IOException ;
20
- import java .util .ArrayList ;
21
- import java .util .Arrays ;
22
- import java .util .Collections ;
23
- import java .util .List ;
24
- import java .util .Set ;
25
- import java .util .concurrent .atomic .AtomicInteger ;
26
- import java .util .concurrent .atomic .AtomicReference ;
27
- import java .util .function .Consumer ;
28
- import java .util .stream .Collectors ;
29
-
30
19
import io .micrometer .context .ContextRegistry ;
31
20
import io .micrometer .context .ContextSnapshotFactory ;
32
21
import io .reactivex .rxjava3 .core .Single ;
35
24
import org .jspecify .annotations .Nullable ;
36
25
import org .junit .jupiter .api .BeforeEach ;
37
26
import org .junit .jupiter .api .Test ;
38
- import reactor .core .publisher .Flux ;
39
- import reactor .core .publisher .Mono ;
40
- import reactor .core .publisher .Sinks ;
41
- import reactor .util .context .ReactorContextAccessor ;
42
-
43
27
import org .springframework .core .MethodParameter ;
44
28
import org .springframework .core .ReactiveAdapterRegistry ;
45
29
import org .springframework .core .ResolvableType ;
51
35
import org .springframework .http .server .ServletServerHttpResponse ;
52
36
import org .springframework .web .accept .ContentNegotiationManager ;
53
37
import org .springframework .web .accept .ContentNegotiationManagerFactoryBean ;
54
- import org .springframework .web .context .request .NativeWebRequest ;
55
- import org .springframework .web .context .request .RequestAttributes ;
56
- import org .springframework .web .context .request .RequestAttributesThreadLocalAccessor ;
57
- import org .springframework .web .context .request .RequestContextHolder ;
58
- import org .springframework .web .context .request .ServletRequestAttributes ;
59
- import org .springframework .web .context .request .ServletWebRequest ;
38
+ import org .springframework .web .context .request .*;
60
39
import org .springframework .web .context .request .async .AsyncWebRequest ;
61
40
import org .springframework .web .context .request .async .StandardServletAsyncWebRequest ;
62
41
import org .springframework .web .context .request .async .WebAsyncUtils ;
63
42
import org .springframework .web .method .support .ModelAndViewContainer ;
64
43
import org .springframework .web .servlet .HandlerMapping ;
65
44
import org .springframework .web .testfixture .servlet .MockHttpServletRequest ;
66
45
import org .springframework .web .testfixture .servlet .MockHttpServletResponse ;
46
+ import reactor .core .publisher .Flux ;
47
+ import reactor .core .publisher .Mono ;
48
+ import reactor .core .publisher .Sinks ;
49
+ import reactor .util .context .ReactorContextAccessor ;
50
+
51
+ import java .io .IOException ;
52
+ import java .nio .charset .StandardCharsets ;
53
+ import java .util .*;
54
+ import java .util .concurrent .atomic .AtomicInteger ;
55
+ import java .util .concurrent .atomic .AtomicReference ;
56
+ import java .util .function .Consumer ;
57
+ import java .util .stream .Collectors ;
67
58
68
59
import static org .assertj .core .api .Assertions .assertThat ;
69
60
import static org .springframework .core .ResolvableType .forClass ;
@@ -240,6 +231,22 @@ void mediaTypes() throws Exception {
240
231
testSseResponse (false );
241
232
}
242
233
234
+ @ Test
235
+ void mediaTypesWithCharset () throws Exception {
236
+
237
+ // Media type from request
238
+ this .servletRequest .addHeader ("Accept" , "text/event-stream;charset=UTF-8" );
239
+ testSseResponse (true );
240
+
241
+ // Media type from "produces" attribute
242
+ Set <MediaType > types = Collections .singleton (new MediaType ("text" , "event-stream" , StandardCharsets .UTF_8 ));
243
+ this .servletRequest .setAttribute (HandlerMapping .PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE , types );
244
+ testSseResponse (true );
245
+
246
+ // No media type preferences
247
+ testSseResponse (false );
248
+ }
249
+
243
250
private void testSseResponse (boolean expectSseEmitter ) throws Exception {
244
251
ResponseBodyEmitter emitter = handleValue (Flux .empty (), Flux .class , forClass (String .class ));
245
252
Object actual = emitter instanceof SseEmitter ;
@@ -265,6 +272,53 @@ void writeServerSentEvents() throws Exception {
265
272
assertThat (emitterHandler .getValuesAsText ()).isEqualTo ("data:foo\n \n data:bar\n \n data:baz\n \n " );
266
273
}
267
274
275
+ @ Test
276
+ void writeServerSentEventsWhenAcceptIsNotSseMediaType () throws Exception {
277
+
278
+ // Media type from request
279
+ this .servletRequest .addHeader ("Accept" , "application/json;charset=UTF-8" );
280
+
281
+ // When `Accept` isn't `text/event-stream`, but elementClass is ServerSentEvent.
282
+ // We want to get `SseEmitter`.
283
+ testSseResponseForServerSentEvent (true );
284
+
285
+ // No media type preferences
286
+ testSseResponse (false );
287
+ }
288
+
289
+ private void testSseResponseForServerSentEvent (boolean expectSseEmitter ) throws Exception {
290
+ ResolvableType type = ResolvableType .forClassWithGenerics (ServerSentEvent .class , String .class );
291
+ ResponseBodyEmitter emitter = handleValue (Flux .empty (), Flux .class , type );
292
+ Object actual = emitter instanceof SseEmitter ;
293
+ assertThat (actual ).isEqualTo (expectSseEmitter );
294
+ resetRequest ();
295
+ }
296
+
297
+ @ Test
298
+ void writeServerSentEventsWithCharset () throws Exception {
299
+
300
+ this .servletRequest .addHeader ("Accept" , "text/event-stream" );
301
+ Set <MediaType > types = Collections .singleton (new MediaType ("text" , "event-stream" , StandardCharsets .UTF_8 ));
302
+ this .servletRequest .setAttribute (HandlerMapping .PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE , types );
303
+
304
+ Sinks .Many <String > sink = Sinks .many ().unicast ().onBackpressureBuffer ();
305
+ SseEmitter sseEmitter = (SseEmitter ) handleValue (sink .asFlux (), Flux .class , forClass (String .class ));
306
+
307
+ EmitterHandler emitterHandler = new EmitterHandler ();
308
+ sseEmitter .initialize (emitterHandler );
309
+
310
+ ServletServerHttpResponse message = new ServletServerHttpResponse (this .servletResponse );
311
+ sseEmitter .extendResponse (message );
312
+
313
+ sink .tryEmitNext ("foo" );
314
+ sink .tryEmitNext ("bar" );
315
+ sink .tryEmitNext ("baz" );
316
+ sink .tryEmitComplete ();
317
+
318
+ assertThat (message .getHeaders ().getContentType ()).hasToString ("text/event-stream;charset=UTF-8" );
319
+ assertThat (emitterHandler .getValuesAsText ()).isEqualTo ("data:foo\n \n data:bar\n \n data:baz\n \n " );
320
+ }
321
+
268
322
@ Test
269
323
void writeServerSentEventsWithBuilder () throws Exception {
270
324
0 commit comments