diff --git a/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpResponse.java b/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpResponse.java index 6ab8116d6c2..32e00b35b3c 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpResponse.java @@ -16,6 +16,8 @@ package org.springframework.mock.http.server.reactive; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.function.Supplier; import org.reactivestreams.Publisher; @@ -24,11 +26,14 @@ import reactor.core.publisher.Mono; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseCookie; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -106,4 +111,42 @@ public class MockServerHttpResponse implements ServerHttpResponse { return this.bufferFactory; } + /** + * Return the body of the response aggregated and converted to a String + * using the charset of the Content-Type response or otherwise defaulting + * to "UTF-8". + */ + public Mono getBodyAsString() { + Charset charset = getCharset(); + Charset charsetToUse = (charset != null ? charset : StandardCharsets.UTF_8); + return Flux.from(this.body) + .reduce(this.bufferFactory.allocateBuffer(), (previous, current) -> { + previous.write(current); + DataBufferUtils.release(current); + return previous; + }) + .map(buffer -> dumpString(buffer, charsetToUse)); + } + + private static String dumpString(DataBuffer buffer, Charset charset) { + Assert.notNull(charset, "'charset' must not be null"); + byte[] bytes = dumpBytes(buffer); + return new String(bytes, charset); + } + + private static byte[] dumpBytes(DataBuffer buffer) { + Assert.notNull(buffer, "'buffer' must not be null"); + byte[] bytes = new byte[buffer.readableByteCount()]; + buffer.read(bytes); + return bytes; + } + + private Charset getCharset() { + MediaType contentType = getHeaders().getContentType(); + if (contentType != null) { + return contentType.getCharset(); + } + return null; + } + } diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 8c6f20c9cd0..358944e2a51 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -110,13 +110,15 @@ public class EncoderHttpMessageWriter implements HttpMessageWriter { * Used when {@link #write} is called without a concrete content type. * *

By default returns the first of {@link Encoder#getEncodableMimeTypes() - * encodableMimeTypes}, if any. + * encodableMimeTypes} that is concrete({@link MediaType#isConcrete()}), if any. * * @param elementType the type of element for encoding * @return the content type, or {@code null} */ protected MediaType getDefaultContentType(ResolvableType elementType) { - return (!this.writableMediaTypes.isEmpty() ? this.writableMediaTypes.get(0) : null); + return writableMediaTypes.stream() + .filter(MediaType::isConcrete) + .findFirst().orElse(null); } } diff --git a/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTest.java b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTest.java new file mode 100644 index 00000000000..97d518e03af --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.http.codec; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import reactor.core.publisher.Mono; + +import org.springframework.core.ResolvableType; +import org.springframework.core.codec.ByteBufferEncoder; +import org.springframework.core.codec.Encoder; +import org.springframework.http.MediaType; +import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; +import org.springframework.tests.TestSubscriber; +import org.springframework.util.MimeTypeUtils; + +/** + * Unit tests for {@link EncoderHttpMessageWriter}. + * + * @author Marcin Kamionowski + * @author Rossen Stoyanchev + */ +public class EncoderHttpMessageWriterTest { + + private MockServerHttpResponse response = new MockServerHttpResponse(); + + + @Test + public void writableMediaTypes() throws Exception { + EncoderHttpMessageWriter writer = createWriter(new ByteBufferEncoder()); + assertThat(writer.getWritableMediaTypes(), containsInAnyOrder(MimeTypeUtils.ALL)); + } + + @Test + public void supportedMediaTypes() throws Exception { + EncoderHttpMessageWriter writer = createWriter(new ByteBufferEncoder()); + assertTrue(writer.canWrite(ResolvableType.forClass(ByteBuffer.class), MediaType.ALL)); + assertTrue(writer.canWrite(ResolvableType.forClass(ByteBuffer.class), MediaType.TEXT_PLAIN)); + } + + @Test + public void encodeByteBuffer(){ + String payload = "Buffer payload"; + Mono source = Mono.just(ByteBuffer.wrap(payload.getBytes(UTF_8))); + + EncoderHttpMessageWriter writer = createWriter(new ByteBufferEncoder()); + writer.write(source, ResolvableType.forClass(ByteBuffer.class), + MediaType.APPLICATION_OCTET_STREAM, this.response, Collections.emptyMap()); + + assertThat(this.response.getHeaders().getContentType(), is(MediaType.APPLICATION_OCTET_STREAM)); + TestSubscriber.subscribe(this.response.getBodyAsString()).assertComplete().assertValues(payload); + } + + @NotNull + private EncoderHttpMessageWriter createWriter(Encoder encoder) { + return new EncoderHttpMessageWriter<>(encoder); + } + +} \ No newline at end of file diff --git a/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageWriterTests.java index 4a0cbb6d167..7c0b1eaf4f8 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageWriterTests.java @@ -21,17 +21,11 @@ import java.util.Collections; import org.junit.Before; import org.junit.Test; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.ResolvableType; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.core.io.buffer.support.DataBufferTestUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRange; import org.springframework.http.HttpStatus; @@ -82,7 +76,7 @@ public class ResourceHttpMessageWriterTests { assertThat(this.response.getHeaders().getContentLength(), is(39L)); assertThat(this.response.getHeaders().getFirst(HttpHeaders.ACCEPT_RANGES), is("bytes")); - Mono result = reduceToString(this.response.getBody(), this.response.bufferFactory()); + Mono result = this.response.getBodyAsString(); TestSubscriber.subscribe(result).assertComplete().assertValues("Spring Framework test resource content."); } @@ -98,7 +92,7 @@ public class ResourceHttpMessageWriterTests { assertThat(this.response.getHeaders().getFirst(HttpHeaders.ACCEPT_RANGES), is("bytes")); assertThat(this.response.getHeaders().getContentLength(), is(6L)); - Mono result = reduceToString(this.response.getBody(), this.response.bufferFactory()); + Mono result = this.response.getBodyAsString(); TestSubscriber.subscribe(result).assertComplete().assertValues("Spring"); } @@ -113,15 +107,4 @@ public class ResourceHttpMessageWriterTests { assertThat(this.response.getStatusCode(), is(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE)); } - private Mono reduceToString(Publisher buffers, DataBufferFactory bufferFactory) { - - return Flux.from(buffers) - .reduce(bufferFactory.allocateBuffer(), (previous, current) -> { - previous.write(current); - DataBufferUtils.release(current); - return previous; - }) - .map(buffer -> DataBufferTestUtils.dumpString(buffer, StandardCharsets.UTF_8)); - } - } diff --git a/spring-web/src/test/java/org/springframework/http/codec/ResourceRegionHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/ResourceRegionHttpMessageWriterTests.java index 8fa76056bac..c5b60da7c89 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/ResourceRegionHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/ResourceRegionHttpMessageWriterTests.java @@ -16,8 +16,8 @@ package org.springframework.http.codec; -import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -28,17 +28,12 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.ResolvableType; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.core.io.buffer.support.DataBufferTestUtils; import org.springframework.core.io.support.ResourceRegion; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -94,7 +89,7 @@ public class ResourceRegionHttpMessageWriterTests { assertThat(this.response.getHeaders().getFirst(HttpHeaders.CONTENT_RANGE), is("bytes 0-5/39")); assertThat(this.response.getHeaders().getContentLength(), is(6L)); - Mono result = reduceToString(this.response.getBody(), this.response.bufferFactory()); + Mono result = response.getBodyAsString(); TestSubscriber.subscribe(result).assertComplete().assertValues("Spring"); } @@ -118,7 +113,7 @@ public class ResourceRegionHttpMessageWriterTests { HttpHeaders headers = this.response.getHeaders(); assertThat(headers.getContentType().toString(), startsWith("multipart/byteranges;boundary=" + boundary)); - Mono result = reduceToString(this.response.getBody(), this.response.bufferFactory()); + Mono result = response.getBodyAsString(); TestSubscriber .subscribe(result).assertNoError() .assertComplete() @@ -149,15 +144,4 @@ public class ResourceRegionHttpMessageWriterTests { }); } - private Mono reduceToString(Publisher buffers, DataBufferFactory bufferFactory) { - - return Flux.from(buffers) - .reduce(bufferFactory.allocateBuffer(), (previous, current) -> { - previous.write(current); - DataBufferUtils.release(current); - return previous; - }) - .map(buffer -> DataBufferTestUtils.dumpString(buffer, StandardCharsets.UTF_8)); - } - } diff --git a/spring-web/src/test/java/org/springframework/mock/http/server/reactive/test/MockServerHttpResponse.java b/spring-web/src/test/java/org/springframework/mock/http/server/reactive/test/MockServerHttpResponse.java index 5e2b9c16ca6..d95ffc74341 100644 --- a/spring-web/src/test/java/org/springframework/mock/http/server/reactive/test/MockServerHttpResponse.java +++ b/spring-web/src/test/java/org/springframework/mock/http/server/reactive/test/MockServerHttpResponse.java @@ -16,6 +16,8 @@ package org.springframework.mock.http.server.reactive.test; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.function.Supplier; import org.reactivestreams.Publisher; @@ -24,9 +26,12 @@ import reactor.core.publisher.Mono; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.core.io.buffer.support.DataBufferTestUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseCookie; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.LinkedMultiValueMap; @@ -57,6 +62,7 @@ public class MockServerHttpResponse implements ServerHttpResponse { return true; } + @Override public HttpStatus getStatusCode() { return this.status; } @@ -105,4 +111,29 @@ public class MockServerHttpResponse implements ServerHttpResponse { return this.bufferFactory; } + /** + * Return the body of the response aggregated and converted to a String + * using the charset of the Content-Type response or otherwise defaulting + * to "UTF-8". + */ + public Mono getBodyAsString() { + Charset charset = getCharset(); + Charset charsetToUse = (charset != null ? charset : StandardCharsets.UTF_8); + return Flux.from(this.body) + .reduce(this.bufferFactory.allocateBuffer(), (previous, current) -> { + previous.write(current); + DataBufferUtils.release(current); + return previous; + }) + .map(buffer -> DataBufferTestUtils.dumpString(buffer, charsetToUse)); + } + + private Charset getCharset() { + MediaType contentType = getHeaders().getContentType(); + if (contentType != null) { + return contentType.getCharset(); + } + return null; + } + }