From e5158d47e08e9edc68db6b2b59162b33c5819e26 Mon Sep 17 00:00:00 2001 From: soldierkam Date: Wed, 28 Sep 2016 22:27:55 +0200 Subject: [PATCH 1/2] EncoderHttpMessageWriter tries to send wildcard type in response header --- .../http/codec/EncoderHttpMessageWriter.java | 6 +- .../codec/EncoderHttpMessageWriterTest.java | 55 +++++++++++++++++++ .../codec/ResourceHttpMessageWriterTests.java | 21 +------ .../ResourceRegionHttpMessageWriterTests.java | 22 +------- .../reactive/test/MockServerHttpResponse.java | 16 ++++++ 5 files changed, 80 insertions(+), 40 deletions(-) create mode 100644 spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTest.java 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..c79325313ce --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTest.java @@ -0,0 +1,55 @@ +package org.springframework.http.codec; + +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.junit.Test; +import reactor.core.publisher.Mono; + +import org.springframework.core.ResolvableType; +import org.springframework.core.codec.ByteBufferEncoder; +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 + */ +public class EncoderHttpMessageWriterTest { + + private EncoderHttpMessageWriter writer = new EncoderHttpMessageWriter<>(new ByteBufferEncoder()); + + private MockServerHttpResponse response = new MockServerHttpResponse(); + + @Test + public void writableMediaTypes() throws Exception { + assertThat(writer.getWritableMediaTypes(), containsInAnyOrder(MimeTypeUtils.ALL)); + } + + @Test + public void supportedMediaTypes() throws Exception { + 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(StandardCharsets.UTF_8))); + + writer.write(source, ResolvableType.forClass(ByteBuffer.class), + MediaType.APPLICATION_OCTET_STREAM, response, Collections.emptyMap()); + + assertThat(this.response.getHeaders().getContentType(), is(MediaType.APPLICATION_OCTET_STREAM)); + TestSubscriber.subscribe(response.getBodyAsString()).assertComplete().assertValues(payload); + } +} \ 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..eebb170febe 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 = 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 = 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..dc5fc3523b6 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,7 @@ package org.springframework.mock.http.server.reactive.test; +import java.nio.charset.StandardCharsets; import java.util.function.Supplier; import org.reactivestreams.Publisher; @@ -24,7 +25,9 @@ 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.ResponseCookie; @@ -57,6 +60,7 @@ public class MockServerHttpResponse implements ServerHttpResponse { return true; } + @Override public HttpStatus getStatusCode() { return this.status; } @@ -105,4 +109,16 @@ public class MockServerHttpResponse implements ServerHttpResponse { return this.bufferFactory; } + public Mono getBodyAsString() { + + return Flux.from(body) + .reduce(bufferFactory.allocateBuffer(), (previous, current) -> { + previous.write(current); + DataBufferUtils.release(current); + return previous; + }) + .map(buffer -> DataBufferTestUtils.dumpString(buffer, StandardCharsets.UTF_8)); + } + + } From 6cb7d51ee5481c97a4b20c126590d99950e5ec40 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 14 Oct 2016 17:16:23 -0400 Subject: [PATCH 2/2] Polish --- .../reactive/MockServerHttpResponse.java | 43 +++++++++++++++++++ .../codec/EncoderHttpMessageWriterTest.java | 43 +++++++++++++++---- .../codec/ResourceHttpMessageWriterTests.java | 4 +- .../reactive/test/MockServerHttpResponse.java | 23 ++++++++-- 4 files changed, 98 insertions(+), 15 deletions(-) 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/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTest.java b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTest.java index c79325313ce..97d518e03af 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTest.java +++ b/spring-web/src/test/java/org/springframework/http/codec/EncoderHttpMessageWriterTest.java @@ -1,5 +1,21 @@ +/* + * 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.*; @@ -7,11 +23,13 @@ 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; @@ -21,35 +39,42 @@ import org.springframework.util.MimeTypeUtils; * Unit tests for {@link EncoderHttpMessageWriter}. * * @author Marcin Kamionowski + * @author Rossen Stoyanchev */ public class EncoderHttpMessageWriterTest { - private EncoderHttpMessageWriter writer = new EncoderHttpMessageWriter<>(new ByteBufferEncoder()); - 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 { - assertTrue(writer.canWrite(ResolvableType.forClass(ByteBuffer.class), - MediaType.ALL)); - assertTrue(writer.canWrite(ResolvableType.forClass(ByteBuffer.class), - MediaType.TEXT_PLAIN)); + 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(StandardCharsets.UTF_8))); + 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, response, Collections.emptyMap()); + MediaType.APPLICATION_OCTET_STREAM, this.response, Collections.emptyMap()); assertThat(this.response.getHeaders().getContentType(), is(MediaType.APPLICATION_OCTET_STREAM)); - TestSubscriber.subscribe(response.getBodyAsString()).assertComplete().assertValues(payload); + 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 eebb170febe..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 @@ -76,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 = response.getBodyAsString(); + Mono result = this.response.getBodyAsString(); TestSubscriber.subscribe(result).assertComplete().assertValues("Spring Framework test resource content."); } @@ -92,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 = response.getBodyAsString(); + Mono result = this.response.getBodyAsString(); TestSubscriber.subscribe(result).assertComplete().assertValues("Spring"); } 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 dc5fc3523b6..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,7 @@ package org.springframework.mock.http.server.reactive.test; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.function.Supplier; @@ -30,6 +31,7 @@ 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; @@ -109,16 +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() { - - return Flux.from(body) - .reduce(bufferFactory.allocateBuffer(), (previous, current) -> { + 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, StandardCharsets.UTF_8)); + .map(buffer -> DataBufferTestUtils.dumpString(buffer, charsetToUse)); } + private Charset getCharset() { + MediaType contentType = getHeaders().getContentType(); + if (contentType != null) { + return contentType.getCharset(); + } + return null; + } }