Refactor RequestedContentTypeResolverBuilder

The revised builder emphasizes creating a list of resolvers either
built-in or custom with each top-level builder method resulting in
adding a resolver.

By default only the Header resolver is configured.

The path extension resolver is removed altogether to discourage its use
but is trivial to create manually with the helpf of
UriUtils#extractFileExtension + MediaTypeFactory.

Issue: SPR-15639
This commit is contained in:
Rossen Stoyanchev
2017-06-08 20:11:41 -04:00
parent b0e8e7f536
commit 01a92517bd
20 changed files with 355 additions and 722 deletions

View File

@@ -1,132 +0,0 @@
/*
* Copyright 2002-2017 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.web.reactive.accept;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link RequestedContentTypeResolverBuilder}.
*
* @author Rossen Stoyanchev
*/
public class CompositeContentTypeResolverBuilderTests {
@Test
public void defaultSettings() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
MockServerWebExchange exchange = MockServerHttpRequest.get("/flower.gif").toExchange();
assertEquals("Should be able to resolve file extensions by default",
Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange));
exchange = MockServerHttpRequest.get("/flower.foobar").toExchange();
assertEquals("Should ignore unknown extensions by default",
Collections.<MediaType>emptyList(), resolver.resolveMediaTypes(exchange));
exchange = MockServerHttpRequest.get("/flower?format=gif").toExchange();
assertEquals("Should not resolve request parameters by default",
Collections.<MediaType>emptyList(), resolver.resolveMediaTypes(exchange));
exchange = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).toExchange();
assertEquals("Should resolve Accept header by default",
Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange));
}
@Test
public void favorPath() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.favorPathExtension(true)
.mediaType("foo", new MediaType("application", "foo"))
.mediaType("bar", new MediaType("application", "bar"))
.build();
ServerWebExchange exchange = MockServerHttpRequest.get("/flower.foo").toExchange();
assertEquals(Collections.singletonList(new MediaType("application", "foo")),
resolver.resolveMediaTypes(exchange));
exchange = MockServerHttpRequest.get("/flower.bar").toExchange();
assertEquals(Collections.singletonList(new MediaType("application", "bar")),
resolver.resolveMediaTypes(exchange));
exchange = MockServerHttpRequest.get("/flower.gif").toExchange();
assertEquals(Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange));
}
@Test
public void favorParameter() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.favorParameter(true)
.mediaType("json", MediaType.APPLICATION_JSON)
.build();
ServerWebExchange exchange = MockServerHttpRequest.get("/flower?format=json").toExchange();
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), resolver.resolveMediaTypes(exchange));
}
@Test
public void ignoreAcceptHeader() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.ignoreAcceptHeader(true)
.build();
ServerWebExchange exchange = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).toExchange();
assertEquals(Collections.<MediaType>emptyList(), resolver.resolveMediaTypes(exchange));
}
@Test // SPR-10513
public void setDefaultContentType() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.defaultContentType(MediaType.APPLICATION_JSON)
.build();
ServerWebExchange exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange();
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), resolver.resolveMediaTypes(exchange));
}
@Test // SPR-12286
public void setDefaultContentTypeWithStrategy() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.defaultContentTypeResolver(new FixedContentTypeResolver(MediaType.APPLICATION_JSON))
.build();
List<MediaType> expected = Collections.singletonList(MediaType.APPLICATION_JSON);
ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
assertEquals(expected, resolver.resolveMediaTypes(exchange));
exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange();
assertEquals(expected, resolver.resolveMediaTypes(exchange));
}
}

View File

@@ -1,103 +0,0 @@
/*
* Copyright 2002-2017 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.web.reactive.accept;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link AbstractMappingContentTypeResolver}.
* @author Rossen Stoyanchev
*/
public class MappingContentTypeResolverTests {
@Test // SPR-13747
public void resolveCaseInsensitive() {
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("JSoN", mapping);
List<MediaType> mediaTypes = resolver.resolve();
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
}
@Test
public void resolveMediaTypes() throws Exception {
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("json", mapping);
List<MediaType> mediaTypes = resolver.resolve();
assertEquals(1, mediaTypes.size());
assertEquals("application/json", mediaTypes.get(0).toString());
}
@Test
public void resolveNoMatch() throws Exception {
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("blah", Collections.emptyMap());
List<MediaType> mediaTypes = resolver.resolve();
assertEquals(0, mediaTypes.size());
}
@Test
public void resolveNoKey() throws Exception {
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver(null, mapping);
List<MediaType> mediaTypes = resolver.resolve();
assertEquals(0, mediaTypes.size());
}
@Test
public void resolveMediaTypesHandleNoMatch() throws Exception {
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("xml", Collections.emptyMap());
List<MediaType> mediaTypes = resolver.resolve();
assertEquals(1, mediaTypes.size());
assertEquals("application/xml", mediaTypes.get(0).toString());
}
private static class TestMappingContentTypeResolver extends AbstractMappingContentTypeResolver {
private final String key;
TestMappingContentTypeResolver(@Nullable String key, Map<String, MediaType> mapping) {
super(mapping);
this.key = key;
}
public List<MediaType> resolve() throws NotAcceptableStatusException {
return super.resolveMediaTypes(MockServerHttpRequest.get("/").toExchange());
}
@Override
protected String getKey(ServerWebExchange exchange) {
return this.key;
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2002-2017 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.web.reactive.accept;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link ParameterContentTypeResolver}.
* @author Rossen Stoyanchev
*/
public class ParameterContentTypeResolverTests {
@Test
public void noKey() throws Exception {
ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(Collections.emptyMap());
List<MediaType> mediaTypes = resolver.resolveMediaTypes(MockServerHttpRequest.get("/").toExchange());
assertEquals(0, mediaTypes.size());
}
@Test(expected = NotAcceptableStatusException.class)
public void noMatchForKey() throws Exception {
ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(Collections.emptyMap());
List<MediaType> mediaTypes = resolver.resolveMediaTypes(createExchange("blah"));
assertEquals(0, mediaTypes.size());
}
@Test
public void resolveKeyFromRegistrations() throws Exception {
ServerWebExchange exchange = createExchange("html");
Map<String, MediaType> mapping = Collections.emptyMap();
RequestedContentTypeResolver resolver = new ParameterContentTypeResolver(mapping);
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(new MediaType("text", "html")), mediaTypes);
mapping = Collections.singletonMap("HTML", MediaType.APPLICATION_XHTML_XML);
resolver = new ParameterContentTypeResolver(mapping);
mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(new MediaType("application", "xhtml+xml")), mediaTypes);
}
@Test
public void resolveKeyThroughMediaTypeFactory() throws Exception {
ServerWebExchange exchange = createExchange("xls");
RequestedContentTypeResolver resolver = new ParameterContentTypeResolver(Collections.emptyMap());
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(new MediaType("application", "vnd.ms-excel")), mediaTypes);
}
@Test // SPR-13747
public void resolveKeyIsCaseInsensitive() {
ServerWebExchange exchange = createExchange("JSoN");
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
ParameterContentTypeResolver resolver = new ParameterContentTypeResolver(mapping);
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
}
private MockServerWebExchange createExchange(String format) {
return MockServerHttpRequest.get("/path?format=" + format).toExchange();
}
}

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2002-2017 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.web.reactive.accept;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link PathExtensionContentTypeResolver}.
*
* @author Rossen Stoyanchev
*/
public class PathExtensionContentTypeResolverTests {
@Test
public void resolveFromRegistrations() throws Exception {
ServerWebExchange exchange = MockServerHttpRequest.get("/test.html").toExchange();
PathExtensionContentTypeResolver resolver = createResolver();
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(new MediaType("text", "html")), mediaTypes);
Map<String, MediaType> mapping = Collections.singletonMap("HTML", MediaType.APPLICATION_XHTML_XML);
resolver = new PathExtensionContentTypeResolver(mapping);
mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(new MediaType("application", "xhtml+xml")), mediaTypes);
}
@Test
public void resolveFromMediaTypeFactory() throws Exception {
ServerWebExchange exchange = MockServerHttpRequest.get("test.xls").toExchange();
PathExtensionContentTypeResolver resolver = createResolver();
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(new MediaType("application", "vnd.ms-excel")), mediaTypes);
}
@Test // SPR-9390
public void resolveFromFilenameWithEncodedURI() throws Exception {
ServerWebExchange exchange = MockServerHttpRequest.get("/quo%20vadis%3f.html").toExchange();
PathExtensionContentTypeResolver resolver = createResolver();
List<MediaType> result = resolver.resolveMediaTypes(exchange);
assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result);
}
@Test // SPR-10170
public void resolveAndIgnoreUnknownExtension() throws Exception {
ServerWebExchange exchange = MockServerHttpRequest.get("test.foobar").toExchange();
PathExtensionContentTypeResolver resolver = createResolver();
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.<MediaType>emptyList(), mediaTypes);
}
private PathExtensionContentTypeResolver createResolver() {
return new PathExtensionContentTypeResolver(Collections.emptyMap());
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2002-2017 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.web.reactive.accept;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link RequestedContentTypeResolverBuilder}.
* @author Rossen Stoyanchev
*/
public class RequestedContentTypeResolverBuilderTests {
@Test
public void defaultSettings() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
ServerWebExchange exchange = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).toExchange();
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(MediaType.IMAGE_GIF), mediaTypes);
}
@Test
public void parameterResolver() throws Exception {
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
builder.parameterResolver().mediaType("json", MediaType.APPLICATION_JSON);
RequestedContentTypeResolver resolver = builder.build();
ServerWebExchange exchange = MockServerHttpRequest.get("/flower?format=json").toExchange();
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
}
@Test
public void parameterResolverWithCustomParamName() throws Exception {
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
builder.parameterResolver().mediaType("json", MediaType.APPLICATION_JSON).parameterName("s");
RequestedContentTypeResolver resolver = builder.build();
ServerWebExchange exchange = MockServerHttpRequest.get("/flower?s=json").toExchange();
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
}
@Test // SPR-10513
public void fixedResolver() throws Exception {
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
builder.fixedResolver(MediaType.APPLICATION_JSON);
RequestedContentTypeResolver resolver = builder.build();
ServerWebExchange exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange();
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
}
@Test // SPR-12286
public void resolver() throws Exception {
RequestedContentTypeResolverBuilder builder = new RequestedContentTypeResolverBuilder();
builder.resolver(new FixedContentTypeResolver(MediaType.APPLICATION_JSON));
RequestedContentTypeResolver resolver = builder.build();
ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
exchange = MockServerHttpRequest.get("/").accept(MediaType.ALL).toExchange();
mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), mediaTypes);
}
}

View File

@@ -40,7 +40,6 @@ import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.util.MultiValueMap;
@@ -76,6 +75,7 @@ import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM;
import static org.springframework.http.MediaType.APPLICATION_XML;
import static org.springframework.http.MediaType.IMAGE_PNG;
import static org.springframework.http.MediaType.TEXT_PLAIN;
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.get;
/**
* Unit tests for {@link WebFluxConfigurationSupport}.
@@ -102,12 +102,8 @@ public class WebFluxConfigurationSupportTests {
RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class);
assertSame(resolver, mapping.getContentTypeResolver());
ServerWebExchange exchange = MockServerHttpRequest.get("/path.json").toExchange();
List<MediaType> list = Collections.singletonList(MediaType.APPLICATION_JSON);
assertEquals(list, resolver.resolveMediaTypes(exchange));
exchange = MockServerHttpRequest.get("/path.foobar").toExchange();
assertEquals(Collections.emptyList(), resolver.resolveMediaTypes(exchange));
ServerWebExchange exchange = get("/path").accept(MediaType.APPLICATION_JSON).toExchange();
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), resolver.resolveMediaTypes(exchange));
}
@Test

View File

@@ -52,11 +52,13 @@ import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import static org.junit.Assert.*;
import static org.springframework.core.io.buffer.support.DataBufferTestUtils.*;
import static org.springframework.http.MediaType.*;
import static org.springframework.web.method.ResolvableMethod.*;
import static org.springframework.web.reactive.HandlerMapping.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.springframework.core.io.buffer.support.DataBufferTestUtils.dumpString;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.web.method.ResolvableMethod.on;
import static org.springframework.web.reactive.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
/**
* Unit tests for {@link AbstractMessageWriterResultHandler}.

View File

@@ -29,8 +29,8 @@ import rx.Single;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.stereotype.Controller;

View File

@@ -44,8 +44,8 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;