private HttpMessageWriter<String> getWriter(Flux<DataBuffer> encodedStream, MimeType... mimeTypes) { List<MimeType> typeList = Arrays.asList(mimeTypes); when(this.encoder.getEncodableMimeTypes()).thenReturn(typeList); when(this.encoder.encode(any(), any(), any(), this.mediaTypeCaptor.capture(), any())).thenReturn(encodedStream); return new EncoderHttpMessageWriter<>(this.encoder); }
/** * Test a standard {@link Encoder#encode encode} scenario. * * @param input the input to be provided to the encoder * @param inputType the input type * @param stepConsumer a consumer to {@linkplain StepVerifier verify} the output * @param mimeType the mime type to use for decoding. May be {@code null}. * @param hints the hints used for decoding. May be {@code null}. * @param <T> the output type */ @SuppressWarnings("unchecked") protected <T> void testEncode(Publisher<? extends T> input, ResolvableType inputType, Consumer<StepVerifier.FirstStep<DataBuffer>> stepConsumer, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { Flux<DataBuffer> result = encoder().encode(input, this.bufferFactory, inputType, mimeType, hints); StepVerifier.FirstStep<DataBuffer> step = StepVerifier.create(result); stepConsumer.accept(step); }
@SuppressWarnings("unchecked") private <T> Flux<DataBuffer> encodeData(@Nullable T data, ResolvableType valueType, MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) { if (data == null) { return Flux.empty(); } if (data instanceof String) { String text = (String) data; return Flux.from(encodeText(StringUtils.replace(text, "\n", "\ndata:") + "\n", mediaType, factory)); } if (this.encoder == null) { return Flux.error(new CodecException("No SSE encoder configured and the data is not String.")); } return ((Encoder<T>) this.encoder) .encode(Mono.just(data), factory, valueType, mediaType, hints) .concatWith(encodeText("\n", mediaType, factory)); }
@SuppressWarnings("unchecked") @Override public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType elementType, @Nullable MediaType mediaType, ReactiveHttpOutputMessage message, Map<String, Object> hints) { MediaType contentType = updateContentType(message, mediaType); Flux<DataBuffer> body = this.encoder.encode( inputStream, message.bufferFactory(), elementType, contentType, hints); if (inputStream instanceof Mono) { HttpHeaders headers = message.getHeaders(); return Mono.from(body) .switchIfEmpty(Mono.defer(() -> { headers.setContentLength(0); return message.setComplete().then(Mono.empty()); })) .flatMap(buffer -> { headers.setContentLength(buffer.readableByteCount()); return message.writeWith(Mono.just(buffer)); }); } return (isStreamingMediaType(contentType) ? message.writeAndFlushWith(body.map(Flux::just)) : message.writeWith(body)); }
@Override protected void testEncodeError(Publisher<?> input, ResolvableType outputType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { Flux<Resource> i = Flux.error(new InputException()); Flux<DataBuffer> result = ((Encoder<Resource>) this.encoder).encode(i, this.bufferFactory, outputType, mimeType, hints); StepVerifier.create(result) .expectError(InputException.class) .verify(); }
/** * Test a {@link Encoder#encode encode} scenario where the input stream is empty. * The output is expected to be empty as well. * * @param inputType the input type * @param mimeType the mime type to use for decoding. May be {@code null}. * @param hints the hints used for decoding. May be {@code null}. */ protected void testEncodeEmpty(ResolvableType inputType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { Flux<?> input = Flux.empty(); Flux<DataBuffer> result = encoder().encode(input, this.bufferFactory, inputType, mimeType, hints); StepVerifier.create(result) .verifyComplete(); }
@SuppressWarnings("unchecked") private <T> Flux<DataBuffer> encodeData(@Nullable T data, ResolvableType valueType, MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) { if (data == null) { return Flux.empty(); } if (data instanceof String) { String text = (String) data; return Flux.from(encodeText(StringUtils.replace(text, "\n", "\ndata:") + "\n", mediaType, factory)); } if (this.encoder == null) { return Flux.error(new CodecException("No SSE encoder configured and the data is not String.")); } return ((Encoder<T>) this.encoder) .encode(Mono.just(data), factory, valueType, mediaType, hints) .concatWith(encodeText("\n", mediaType, factory)); }
/** * Test a {@link Encoder#encode encode} scenario where the input stream is canceled. * This test method will feed the first element of the {@code input} stream to the decoder, * followed by a cancel signal. * The result is expected to contain one "normal" element. * * @param input the input to be provided to the encoder * @param inputType the input type * @param mimeType the mime type to use for decoding. May be {@code null}. * @param hints the hints used for decoding. May be {@code null}. */ protected void testEncodeCancel(Publisher<?> input, ResolvableType inputType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { Flux<DataBuffer> result = encoder().encode(input, this.bufferFactory, inputType, mimeType, hints); StepVerifier.create(result) .consumeNextWith(DataBufferUtils::release) .thenCancel() .verify(); }
@SuppressWarnings("unchecked") @Override public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType elementType, @Nullable MediaType mediaType, ReactiveHttpOutputMessage message, Map<String, Object> hints) { MediaType contentType = updateContentType(message, mediaType); Flux<DataBuffer> body = this.encoder.encode( inputStream, message.bufferFactory(), elementType, contentType, hints); if (inputStream instanceof Mono) { HttpHeaders headers = message.getHeaders(); return Mono.from(body) .switchIfEmpty(Mono.defer(() -> { headers.setContentLength(0); return message.setComplete().then(Mono.empty()); })) .flatMap(buffer -> { headers.setContentLength(buffer.readableByteCount()); return message.writeWith(Mono.just(buffer)); }); } return (isStreamingMediaType(contentType) ? message.writeAndFlushWith(body.map(Flux::just)) : message.writeWith(body)); }
/** * Test a {@link Encoder#encode encode} scenario where the input stream contains an error. * This test method will feed the first element of the {@code input} stream to the encoder, * followed by an {@link InputException}. * The result is expected to contain one "normal" element, followed by the error. * * @param input the input to be provided to the encoder * @param inputType the input type * @param mimeType the mime type to use for decoding. May be {@code null}. * @param hints the hints used for decoding. May be {@code null}. * @see InputException */ protected void testEncodeError(Publisher<?> input, ResolvableType inputType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { input = Flux.concat( Flux.from(input).take(1), Flux.error(new InputException())); Flux<DataBuffer> result = encoder().encode(input, this.bufferFactory, inputType, mimeType, hints); StepVerifier.create(result) .consumeNextWith(DataBufferUtils::release) .expectError(InputException.class) .verify(); }
@SuppressWarnings("unchecked") private <T> Flux<DataBuffer> encodeData(@Nullable T data, ResolvableType valueType, MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) { if (data == null) { return Flux.empty(); } if (data instanceof String) { String text = (String) data; return Flux.from(encodeText(text.replaceAll("\\n", "\ndata:") + "\n", mediaType, factory)); } if (this.encoder == null) { return Flux.error(new CodecException("No SSE encoder configured and the data is not String.")); } return ((Encoder<T>) this.encoder) .encode(Mono.just(data), factory, valueType, mediaType, hints) .concatWith(encodeText("\n", mediaType, factory)); }
@SuppressWarnings("unchecked") private <T> Flux<DataBuffer> encodeData(@Nullable T data, ResolvableType valueType, MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) { if (data == null) { return Flux.empty(); } if (data instanceof String) { String text = (String) data; return Flux.from(encodeText(StringUtils.replace(text, "\n", "\ndata:") + "\n", mediaType, factory)); } if (this.encoder == null) { return Flux.error(new CodecException("No SSE encoder configured and the data is not String.")); } return ((Encoder<T>) this.encoder) .encode(Mono.just(data), factory, valueType, mediaType, hints) .concatWith(encodeText("\n", mediaType, factory)); }
@SuppressWarnings("unchecked") @Override public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType elementType, @Nullable MediaType mediaType, ReactiveHttpOutputMessage message, Map<String, Object> hints) { MediaType contentType = updateContentType(message, mediaType); Flux<DataBuffer> body = this.encoder.encode( inputStream, message.bufferFactory(), elementType, contentType, hints); if (inputStream instanceof Mono) { HttpHeaders headers = message.getHeaders(); return Mono.from(body) .switchIfEmpty(Mono.defer(() -> { headers.setContentLength(0); return message.setComplete().then(Mono.empty()); })) .flatMap(buffer -> { headers.setContentLength(buffer.readableByteCount()); return message.writeWith(Mono.just(buffer)); }); } return (isStreamingMediaType(contentType) ? message.writeAndFlushWith(body.map(Flux::just)) : message.writeWith(body)); }
Flux<DataBuffer> body = this.encoder.encode( inputStream, message.bufferFactory(), elementType, contentType, hints);