Rename spring-web-reactive to spring-webflux

Issue: SPR-15190
This commit is contained in:
Rossen Stoyanchev
2017-02-01 17:02:52 -05:00
parent 81d1217976
commit fafd2d20e1
386 changed files with 22 additions and 25 deletions

View File

@@ -0,0 +1,243 @@
/*
* 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.web.reactive;
import java.time.Duration;
import java.util.Collections;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.http.HttpStatus;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.WebExceptionHandler;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.handler.ExceptionHandlingWebHandler;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.springframework.http.MediaType.APPLICATION_JSON;
/**
* Test the effect of exceptions at different stages of request processing by
* checking the error signals on the completion publisher.
*
* @author Rossen Stoyanchev
*/
@SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "ThrowableInstanceNeverThrown"})
public class DispatcherHandlerErrorTests {
private static final IllegalStateException EXCEPTION = new IllegalStateException("boo");
private DispatcherHandler dispatcherHandler;
private MockServerHttpRequest request;
@Before
public void setUp() throws Exception {
AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext();
appContext.register(TestConfig.class);
appContext.refresh();
this.dispatcherHandler = new DispatcherHandler(appContext);
}
@Test
public void noHandler() throws Exception {
this.request = MockServerHttpRequest.get("/does-not-exist").build();
Mono<Void> publisher = this.dispatcherHandler.handle(createExchange());
StepVerifier.create(publisher)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(ResponseStatusException.class));
assertThat(error.getMessage(),
is("Request failure [status: 404, reason: \"No matching handler\"]"));
})
.verify();
}
@Test
public void controllerReturnsMonoError() throws Exception {
this.request = MockServerHttpRequest.get("/error-signal").build();
Mono<Void> publisher = this.dispatcherHandler.handle(createExchange());
StepVerifier.create(publisher)
.consumeErrorWith(error -> assertSame(EXCEPTION, error))
.verify();
}
@Test
public void controllerThrowsException() throws Exception {
this.request = MockServerHttpRequest.get("/raise-exception").build();
Mono<Void> publisher = this.dispatcherHandler.handle(createExchange());
StepVerifier.create(publisher)
.consumeErrorWith(error -> assertSame(EXCEPTION, error))
.verify();
}
@Test
public void unknownReturnType() throws Exception {
this.request = MockServerHttpRequest.get("/unknown-return-type").build();
Mono<Void> publisher = this.dispatcherHandler.handle(createExchange());
StepVerifier.create(publisher)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(IllegalStateException.class));
assertThat(error.getMessage(), startsWith("No HandlerResultHandler"));
})
.verify();
}
@Test
public void responseBodyMessageConversionError() throws Exception {
this.request = MockServerHttpRequest.post("/request-body").accept(APPLICATION_JSON).body("body");
Mono<Void> publisher = this.dispatcherHandler.handle(createExchange());
StepVerifier.create(publisher)
.consumeErrorWith(error -> assertThat(error, instanceOf(NotAcceptableStatusException.class)))
.verify();
}
@Test
public void requestBodyError() throws Exception {
this.request = MockServerHttpRequest.post("/request-body").body(Mono.error(EXCEPTION));
Mono<Void> publisher = this.dispatcherHandler.handle(createExchange());
StepVerifier.create(publisher)
.consumeErrorWith(error -> {
assertThat(error, instanceOf(ServerWebInputException.class));
assertSame(EXCEPTION, error.getCause());
})
.verify();
}
@Test
public void webExceptionHandler() throws Exception {
this.request = MockServerHttpRequest.get("/unknown-argument-type").build();
ServerWebExchange exchange = createExchange();
WebExceptionHandler exceptionHandler = new ServerError500ExceptionHandler();
WebHandler webHandler = new ExceptionHandlingWebHandler(this.dispatcherHandler, exceptionHandler);
webHandler.handle(exchange).block(Duration.ofSeconds(5));
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, exchange.getResponse().getStatusCode());
}
@NotNull
private ServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
@Configuration
@SuppressWarnings({"unused", "WeakerAccess"})
static class TestConfig {
@Bean
public RequestMappingHandlerMapping handlerMapping() {
return new RequestMappingHandlerMapping();
}
@Bean
public RequestMappingHandlerAdapter handlerAdapter() {
return new RequestMappingHandlerAdapter();
}
@Bean
public ResponseBodyResultHandler resultHandler() {
return new ResponseBodyResultHandler(
Collections.singletonList(new EncoderHttpMessageWriter<>(new CharSequenceEncoder())),
new HeaderContentTypeResolver());
}
@Bean
public TestController testController() {
return new TestController();
}
}
@Controller
@SuppressWarnings("unused")
private static class TestController {
@RequestMapping("/error-signal")
@ResponseBody
public Publisher<String> errorSignal() {
return Mono.error(EXCEPTION);
}
@RequestMapping("/raise-exception")
public void raiseException() throws Exception {
throw EXCEPTION;
}
@RequestMapping("/unknown-return-type")
public Foo unknownReturnType() throws Exception {
return new Foo();
}
@RequestMapping("/request-body")
@ResponseBody
public Publisher<String> requestBody(@RequestBody Publisher<String> body) {
return Mono.from(body).map(s -> "hello " + s);
}
}
private static class Foo {
}
private static class ServerError500ExceptionHandler implements WebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
return Mono.empty();
}
}
}

View File

@@ -0,0 +1,154 @@
/*
* 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;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.bootstrap.RxNettyHttpServer;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.ExchangeFunctions;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
import static org.junit.Assert.assertTrue;
/**
* @author Sebastien Deleuze
*/
public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private WebClient webClient;
@Before
public void setup() throws Exception {
// TODO: fix failing RxNetty tests
Assume.assumeFalse(this.server instanceof RxNettyHttpServer);
super.setup();
this.webClient = WebClient.create("http://localhost:" + this.port);
}
@Test
public void writeAndFlushWith() throws Exception {
Mono<String> result = this.webClient.get()
.uri("/write-and-flush")
.exchange()
.flatMap(response -> response.body(BodyExtractors.toFlux(String.class)))
.takeUntil(s -> s.endsWith("data1"))
.reduce((s1, s2) -> s1 + s2);
StepVerifier.create(result)
.expectNext("data0data1")
.expectComplete()
.verify(Duration.ofSeconds(10L));
}
@Test // SPR-14991
public void writeAndAutoFlushOnComplete() {
Mono<String> result = this.webClient.get()
.uri("/write-and-complete")
.exchange()
.flatMap(response -> response.bodyToFlux(String.class))
.reduce((s1, s2) -> s1 + s2);
StepVerifier.create(result)
.consumeNextWith(value -> assertTrue(value.length() == 200000))
.expectComplete()
.verify(Duration.ofSeconds(10L));
}
@Test // SPR-14992
public void writeAndAutoFlushBeforeComplete() {
Flux<String> result = this.webClient.get()
.uri("/write-and-never-complete")
.exchange()
.flatMap(response -> response.bodyToFlux(String.class));
StepVerifier.create(result)
.expectNextMatches(s -> s.startsWith("0123456789"))
.thenCancel()
.verify(Duration.ofSeconds(10L));
}
@Override
protected HttpHandler createHttpHandler() {
return new FlushingHandler();
}
private static class FlushingHandler implements HttpHandler {
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
String path = request.getURI().getPath();
if (path.endsWith("write-and-flush")) {
Flux<Publisher<DataBuffer>> responseBody = Flux
.intervalMillis(50)
.map(l -> toDataBuffer("data" + l, response.bufferFactory()))
.take(2)
.map(Flux::just);
responseBody = responseBody.concatWith(Flux.never());
return response.writeAndFlushWith(responseBody);
}
else if (path.endsWith("write-and-complete")) {
Flux<DataBuffer> responseBody = Flux
.just("0123456789")
.repeat(20000)
.map(value -> toDataBuffer(value, response.bufferFactory()));
return response.writeWith(responseBody);
}
else if (path.endsWith("write-and-never-complete")) {
Flux<DataBuffer> responseBody = Flux
.just("0123456789")
.repeat(20000)
.map(value -> toDataBuffer(value, response.bufferFactory()))
.mergeWith(Flux.never());
return response.writeWith(responseBody);
}
return response.writeWith(Flux.empty());
}
private DataBuffer toDataBuffer(String value, DataBufferFactory factory) {
byte[] data = (value).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = factory.allocateBuffer(data.length);
buffer.write(data);
return buffer;
}
}
}

View File

@@ -0,0 +1,180 @@
/*
* 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.web.reactive.accept;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
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();
ServerWebExchange exchange = createExchange("/flower.gif");
assertEquals("Should be able to resolve file extensions by default",
Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange));
exchange = createExchange("/flower.xyz");
assertEquals("Should ignore unknown extensions by default",
Collections.<MediaType>emptyList(), resolver.resolveMediaTypes(exchange));
exchange = createExchange("/flower?format=gif");
assertEquals("Should not resolve request parameters by default",
Collections.<MediaType>emptyList(), resolver.resolveMediaTypes(exchange));
ServerHttpRequest request = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).build();
exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
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 = createExchange("/flower.foo");
assertEquals(Collections.singletonList(new MediaType("application", "foo")),
resolver.resolveMediaTypes(exchange));
exchange = createExchange("/flower.bar");
assertEquals(Collections.singletonList(new MediaType("application", "bar")),
resolver.resolveMediaTypes(exchange));
exchange = createExchange("/flower.gif");
assertEquals(Collections.singletonList(MediaType.IMAGE_GIF), resolver.resolveMediaTypes(exchange));
}
@Test
public void favorPathWithJafTurnedOff() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.favorPathExtension(true)
.useJaf(false)
.build();
ServerWebExchange exchange = createExchange("/flower.foo");
assertEquals(Collections.emptyList(), resolver.resolveMediaTypes(exchange));
exchange = createExchange("/flower.gif");
assertEquals(Collections.emptyList(), resolver.resolveMediaTypes(exchange));
}
@Test(expected = NotAcceptableStatusException.class) // SPR-10170
public void favorPathWithIgnoreUnknownPathExtensionTurnedOff() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.favorPathExtension(true)
.ignoreUnknownPathExtensions(false)
.build();
ServerWebExchange exchange = createExchange("/flower.xyz?format=json");
resolver.resolveMediaTypes(exchange);
}
@Test
public void favorParameter() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.favorParameter(true)
.mediaType("json", MediaType.APPLICATION_JSON)
.build();
ServerWebExchange exchange = createExchange("/flower?format=json");
assertEquals(Collections.singletonList(MediaType.APPLICATION_JSON), resolver.resolveMediaTypes(exchange));
}
@Test(expected = NotAcceptableStatusException.class) // SPR-10170
public void favorParameterWithUnknownMediaType() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.favorParameter(true)
.build();
ServerWebExchange exchange = createExchange("/flower?format=xyz");
resolver.resolveMediaTypes(exchange);
}
@Test
public void ignoreAcceptHeader() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.ignoreAcceptHeader(true)
.build();
ServerHttpRequest request = MockServerHttpRequest.get("/flower").accept(MediaType.IMAGE_GIF).build();
ServerWebExchange exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
assertEquals(Collections.<MediaType>emptyList(), resolver.resolveMediaTypes(exchange));
}
@Test // SPR-10513
public void setDefaultContentType() throws Exception {
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder()
.defaultContentType(MediaType.APPLICATION_JSON)
.build();
ServerHttpRequest request = MockServerHttpRequest.get("/").accept(MediaType.ALL).build();
ServerWebExchange exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
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 = createExchange("/");
assertEquals(expected, resolver.resolveMediaTypes(exchange));
ServerHttpRequest request = MockServerHttpRequest.get("/").accept(MediaType.ALL).build();
exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
assertEquals(expected, resolver.resolveMediaTypes(exchange));
}
private ServerWebExchange createExchange(String url) throws URISyntaxException {
ServerHttpRequest request = MockServerHttpRequest.get(url).build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.web.reactive.accept;
import java.net.URISyntaxException;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link HeaderContentTypeResolver}.
*
* @author Rossen Stoyanchev
*/
public class HeaderContentTypeResolverTests {
private HeaderContentTypeResolver resolver;
@Before
public void setup() {
this.resolver = new HeaderContentTypeResolver();
}
@Test
public void resolveMediaTypes() throws Exception {
ServerWebExchange exchange = createExchange("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c");
List<MediaType> mediaTypes = this.resolver.resolveMediaTypes(exchange);
assertEquals(4, mediaTypes.size());
assertEquals("text/html", mediaTypes.get(0).toString());
assertEquals("text/x-c", mediaTypes.get(1).toString());
assertEquals("text/x-dvi;q=0.8", mediaTypes.get(2).toString());
assertEquals("text/plain;q=0.5", mediaTypes.get(3).toString());
}
@Test(expected = NotAcceptableStatusException.class)
public void resolveMediaTypesParseError() throws Exception {
ServerWebExchange exchange = createExchange("textplain; q=0.5");
this.resolver.resolveMediaTypes(exchange);
}
private ServerWebExchange createExchange(String accept) throws URISyntaxException {
ServerHttpRequest request = (accept != null ?
MockServerHttpRequest.get("/").header("accept", accept).build() :
MockServerHttpRequest.get("/").build());
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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.web.reactive.accept;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link AbstractMappingContentTypeResolver}.
* @author Rossen Stoyanchev
*/
public class MappingContentTypeResolverTests {
@Test
public void resolveExtensions() {
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("", mapping);
Set<String> keys = resolver.getKeysFor(MediaType.APPLICATION_JSON);
assertEquals(1, keys.size());
assertEquals("json", keys.iterator().next());
}
@Test
public void resolveExtensionsNoMatch() {
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("", mapping);
Set<String> keys = resolver.getKeysFor(MediaType.TEXT_HTML);
assertTrue(keys.isEmpty());
}
@Test // SPR-13747
public void lookupMediaTypeCaseInsensitive() {
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("", mapping);
MediaType mediaType = resolver.getMediaType("JSoN");
assertEquals(mediaType, MediaType.APPLICATION_JSON);
}
@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.resolveMediaTypes((ServerWebExchange) null);
assertEquals(1, mediaTypes.size());
assertEquals("application/json", mediaTypes.get(0).toString());
}
@Test
public void resolveMediaTypesNoMatch() throws Exception {
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("blah", null);
List<MediaType> mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null);
assertEquals(0, mediaTypes.size());
}
@Test
public void resolveMediaTypesNoKey() throws Exception {
Map<String, MediaType> mapping = Collections.singletonMap("json", MediaType.APPLICATION_JSON);
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver(null, mapping);
List<MediaType> mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null);
assertEquals(0, mediaTypes.size());
}
@Test
public void resolveMediaTypesHandleNoMatch() throws Exception {
TestMappingContentTypeResolver resolver = new TestMappingContentTypeResolver("xml", null);
List<MediaType> mediaTypes = resolver.resolveMediaTypes((ServerWebExchange) null);
assertEquals(1, mediaTypes.size());
assertEquals("application/xml", mediaTypes.get(0).toString());
}
private static class TestMappingContentTypeResolver extends AbstractMappingContentTypeResolver {
private final String key;
public TestMappingContentTypeResolver(String key, Map<String, MediaType> mapping) {
super(mapping);
this.key = key;
}
@Override
protected String extractKey(ServerWebExchange exchange) {
return this.key;
}
@Override
protected MediaType handleNoMatch(String mappingKey) {
return "xml".equals(mappingKey) ? MediaType.APPLICATION_XML : null;
}
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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.web.reactive.accept;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link PathExtensionContentTypeResolver}.
*
* @author Rossen Stoyanchev
*/
public class PathExtensionContentTypeResolverTests {
@Test
public void resolveMediaTypesFromMapping() throws Exception {
ServerWebExchange exchange = createExchange("/test.html");
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
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 resolveMediaTypesFromJaf() throws Exception {
ServerWebExchange exchange = createExchange("test.xls");
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.singletonList(new MediaType("application", "vnd.ms-excel")), mediaTypes);
}
// SPR-10334
@Test
public void getMediaTypeFromFilenameNoJaf() throws Exception {
ServerWebExchange exchange = createExchange("test.json");
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
resolver.setUseJaf(false);
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.<MediaType>emptyList(), mediaTypes);
}
// SPR-9390
@Test
public void getMediaTypeFilenameWithEncodedURI() throws Exception {
ServerWebExchange exchange = createExchange("/quo%20vadis%3f.html");
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
List<MediaType> result = resolver.resolveMediaTypes(exchange);
assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result);
}
// SPR-10170
@Test
public void resolveMediaTypesIgnoreUnknownExtension() throws Exception {
ServerWebExchange exchange = createExchange("test.xyz");
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
assertEquals(Collections.<MediaType>emptyList(), mediaTypes);
}
@Test(expected = NotAcceptableStatusException.class)
public void resolveMediaTypesDoNotIgnoreUnknownExtension() throws Exception {
ServerWebExchange exchange = createExchange("test.xyz");
PathExtensionContentTypeResolver resolver = new PathExtensionContentTypeResolver();
resolver.setIgnoreUnknownExtensions(false);
resolver.resolveMediaTypes(exchange);
}
private ServerWebExchange createExchange(String path) throws URISyntaxException {
ServerHttpRequest request = MockServerHttpRequest.get(path).build();
WebSessionManager sessionManager = new MockWebSessionManager();
return new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2015 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.config;
import java.util.Arrays;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.springframework.web.cors.CorsConfiguration;
/**
* Test fixture with a {@link CorsRegistry}.
*
* @author Sebastien Deleuze
*/
public class CorsRegistryTests {
private CorsRegistry registry;
@Before
public void setUp() {
this.registry = new CorsRegistry();
}
@Test
public void noMapping() {
assertTrue(this.registry.getCorsConfigurations().isEmpty());
}
@Test
public void multipleMappings() {
this.registry.addMapping("/foo");
this.registry.addMapping("/bar");
assertEquals(2, this.registry.getCorsConfigurations().size());
}
@Test
public void customizedMapping() {
this.registry.addMapping("/foo").allowedOrigins("http://domain2.com", "http://domain2.com")
.allowedMethods("DELETE").allowCredentials(false).allowedHeaders("header1", "header2")
.exposedHeaders("header3", "header4").maxAge(3600);
Map<String, CorsConfiguration> configs = this.registry.getCorsConfigurations();
assertEquals(1, configs.size());
CorsConfiguration config = configs.get("/foo");
assertEquals(Arrays.asList("http://domain2.com", "http://domain2.com"), config.getAllowedOrigins());
assertEquals(Arrays.asList("DELETE"), config.getAllowedMethods());
assertEquals(Arrays.asList("header1", "header2"), config.getAllowedHeaders());
assertEquals(Arrays.asList("header3", "header4"), config.getExposedHeaders());
assertEquals(false, config.getAllowCredentials());
assertEquals(Long.valueOf(3600), config.getMaxAge());
}
}

View File

@@ -0,0 +1,142 @@
/*
* 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.config;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.doAnswer;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.verify;
/**
* Test fixture for {@link DelegatingWebReactiveConfiguration} tests.
*
* @author Brian Clozel
*/
public class DelegatingWebReactiveConfigurationTests {
private DelegatingWebReactiveConfiguration delegatingConfig;
@Mock
private WebReactiveConfigurer webReactiveConfigurer;
@Captor
private ArgumentCaptor<List<HttpMessageReader<?>>> readers;
@Captor
private ArgumentCaptor<List<HttpMessageWriter<?>>> writers;
@Captor
private ArgumentCaptor<FormatterRegistry> formatterRegistry;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
delegatingConfig = new DelegatingWebReactiveConfiguration();
delegatingConfig.setApplicationContext(new StaticApplicationContext());
given(webReactiveConfigurer.getValidator()).willReturn(Optional.empty());
given(webReactiveConfigurer.getMessageCodesResolver()).willReturn(Optional.empty());
}
@Test
public void requestMappingHandlerMapping() throws Exception {
delegatingConfig.setConfigurers(Collections.singletonList(webReactiveConfigurer));
delegatingConfig.requestMappingHandlerMapping();
verify(webReactiveConfigurer).configureContentTypeResolver(any(RequestedContentTypeResolverBuilder.class));
verify(webReactiveConfigurer).addCorsMappings(any(CorsRegistry.class));
verify(webReactiveConfigurer).configurePathMatching(any(PathMatchConfigurer.class));
}
@Test
public void requestMappingHandlerAdapter() throws Exception {
delegatingConfig.setConfigurers(Collections.singletonList(webReactiveConfigurer));
RequestMappingHandlerAdapter adapter = delegatingConfig.requestMappingHandlerAdapter();
ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) adapter.getWebBindingInitializer();
ConversionService initializerConversionService = initializer.getConversionService();
assertTrue(initializer.getValidator() instanceof LocalValidatorFactoryBean);
verify(webReactiveConfigurer).configureMessageReaders(readers.capture());
verify(webReactiveConfigurer).extendMessageReaders(readers.capture());
verify(webReactiveConfigurer).getValidator();
verify(webReactiveConfigurer).getMessageCodesResolver();
verify(webReactiveConfigurer).addFormatters(formatterRegistry.capture());
verify(webReactiveConfigurer).addArgumentResolvers(any());
assertSame(formatterRegistry.getValue(), initializerConversionService);
assertEquals(7, readers.getValue().size());
}
@Test
public void resourceHandlerMapping() throws Exception {
delegatingConfig.setConfigurers(Collections.singletonList(webReactiveConfigurer));
doAnswer(invocation -> {
ResourceHandlerRegistry registry = invocation.getArgument(0);
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static");
return null;
}).when(webReactiveConfigurer).addResourceHandlers(any(ResourceHandlerRegistry.class));
delegatingConfig.resourceHandlerMapping();
verify(webReactiveConfigurer).addResourceHandlers(any(ResourceHandlerRegistry.class));
verify(webReactiveConfigurer).configurePathMatching(any(PathMatchConfigurer.class));
}
@Test
public void responseBodyResultHandler() throws Exception {
delegatingConfig.setConfigurers(Collections.singletonList(webReactiveConfigurer));
delegatingConfig.responseBodyResultHandler();
verify(webReactiveConfigurer).configureMessageWriters(writers.capture());
verify(webReactiveConfigurer).extendMessageWriters(writers.capture());
verify(webReactiveConfigurer).configureContentTypeResolver(any(RequestedContentTypeResolverBuilder.class));
}
@Test
public void viewResolutionResultHandler() throws Exception {
delegatingConfig.setConfigurers(Collections.singletonList(webReactiveConfigurer));
delegatingConfig.viewResolutionResultHandler();
verify(webReactiveConfigurer).configureViewResolvers(any(ViewResolverRegistry.class));
}
}

View File

@@ -0,0 +1,229 @@
/*
* 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.config;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import reactor.test.StepVerifier;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
import org.springframework.http.CacheControl;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.resource.AppCacheManifestTransformer;
import org.springframework.web.reactive.resource.CachingResourceResolver;
import org.springframework.web.reactive.resource.CachingResourceTransformer;
import org.springframework.web.reactive.resource.CssLinkResourceTransformer;
import org.springframework.web.reactive.resource.PathResourceResolver;
import org.springframework.web.reactive.resource.ResourceResolver;
import org.springframework.web.reactive.resource.ResourceTransformer;
import org.springframework.web.reactive.resource.ResourceWebHandler;
import org.springframework.web.reactive.resource.VersionResourceResolver;
import org.springframework.web.reactive.resource.WebJarsResourceResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link ResourceHandlerRegistry}.
*
* @author Rossen Stoyanchev
*/
public class ResourceHandlerRegistryTests {
private ResourceHandlerRegistry registry;
private ResourceHandlerRegistration registration;
private ServerWebExchange exchange;
private MockServerHttpResponse response;
@Before
public void setUp() {
this.registry = new ResourceHandlerRegistry(new GenericApplicationContext());
this.registration = this.registry.addResourceHandler("/resources/**");
this.registration.addResourceLocations("classpath:org/springframework/web/reactive/config/");
MockServerHttpRequest request = MockServerHttpRequest.get("").build();
this.response = new MockServerHttpResponse();
this.exchange = new DefaultServerWebExchange(request, this.response);
}
@Test
public void noResourceHandlers() throws Exception {
this.registry = new ResourceHandlerRegistry(new GenericApplicationContext());
assertNull(this.registry.getHandlerMapping());
}
@Test
public void mapPathToLocation() throws Exception {
this.exchange.getAttributes().put(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/testStylesheet.css");
ResourceWebHandler handler = getHandler("/resources/**");
handler.handle(this.exchange).blockMillis(5000);
StepVerifier.create(this.response.getBody())
.consumeNextWith(buf -> assertEquals("test stylesheet content",
DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8)))
.expectComplete()
.verify();
}
@Test
public void cacheControl() {
assertThat(getHandler("/resources/**").getCacheControl(),
Matchers.nullValue());
this.registration.setCacheControl(CacheControl.noCache().cachePrivate());
assertThat(getHandler("/resources/**").getCacheControl().getHeaderValue(),
Matchers.equalTo(CacheControl.noCache().cachePrivate().getHeaderValue()));
}
@Test
public void order() {
assertEquals(Integer.MAX_VALUE -1, this.registry.getHandlerMapping().getOrder());
this.registry.setOrder(0);
assertEquals(0, this.registry.getHandlerMapping().getOrder());
}
@Test
public void hasMappingForPattern() {
assertTrue(this.registry.hasMappingForPattern("/resources/**"));
assertFalse(this.registry.hasMappingForPattern("/whatever"));
}
@Test
public void resourceChain() throws Exception {
ResourceResolver mockResolver = Mockito.mock(ResourceResolver.class);
ResourceTransformer mockTransformer = Mockito.mock(ResourceTransformer.class);
this.registration.resourceChain(true).addResolver(mockResolver).addTransformer(mockTransformer);
ResourceWebHandler handler = getHandler("/resources/**");
List<ResourceResolver> resolvers = handler.getResourceResolvers();
assertThat(resolvers.toString(), resolvers, Matchers.hasSize(4));
assertThat(resolvers.get(0), Matchers.instanceOf(CachingResourceResolver.class));
CachingResourceResolver cachingResolver = (CachingResourceResolver) resolvers.get(0);
assertThat(cachingResolver.getCache(), Matchers.instanceOf(ConcurrentMapCache.class));
assertThat(resolvers.get(1), Matchers.equalTo(mockResolver));
assertThat(resolvers.get(2), Matchers.instanceOf(WebJarsResourceResolver.class));
assertThat(resolvers.get(3), Matchers.instanceOf(PathResourceResolver.class));
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers, Matchers.hasSize(2));
assertThat(transformers.get(0), Matchers.instanceOf(CachingResourceTransformer.class));
assertThat(transformers.get(1), Matchers.equalTo(mockTransformer));
}
@Test
public void resourceChainWithoutCaching() throws Exception {
this.registration.resourceChain(false);
ResourceWebHandler handler = getHandler("/resources/**");
List<ResourceResolver> resolvers = handler.getResourceResolvers();
assertThat(resolvers, Matchers.hasSize(2));
assertThat(resolvers.get(0), Matchers.instanceOf(WebJarsResourceResolver.class));
assertThat(resolvers.get(1), Matchers.instanceOf(PathResourceResolver.class));
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers, Matchers.hasSize(0));
}
@Test
public void resourceChainWithVersionResolver() throws Exception {
VersionResourceResolver versionResolver = new VersionResourceResolver()
.addFixedVersionStrategy("fixed", "/**/*.js")
.addContentVersionStrategy("/**");
this.registration.resourceChain(true).addResolver(versionResolver)
.addTransformer(new AppCacheManifestTransformer());
ResourceWebHandler handler = getHandler("/resources/**");
List<ResourceResolver> resolvers = handler.getResourceResolvers();
assertThat(resolvers.toString(), resolvers, Matchers.hasSize(4));
assertThat(resolvers.get(0), Matchers.instanceOf(CachingResourceResolver.class));
assertThat(resolvers.get(1), Matchers.sameInstance(versionResolver));
assertThat(resolvers.get(2), Matchers.instanceOf(WebJarsResourceResolver.class));
assertThat(resolvers.get(3), Matchers.instanceOf(PathResourceResolver.class));
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers, Matchers.hasSize(3));
assertThat(transformers.get(0), Matchers.instanceOf(CachingResourceTransformer.class));
assertThat(transformers.get(1), Matchers.instanceOf(CssLinkResourceTransformer.class));
assertThat(transformers.get(2), Matchers.instanceOf(AppCacheManifestTransformer.class));
}
@Test
public void resourceChainWithOverrides() throws Exception {
CachingResourceResolver cachingResolver = Mockito.mock(CachingResourceResolver.class);
VersionResourceResolver versionResolver = Mockito.mock(VersionResourceResolver.class);
WebJarsResourceResolver webjarsResolver = Mockito.mock(WebJarsResourceResolver.class);
PathResourceResolver pathResourceResolver = new PathResourceResolver();
CachingResourceTransformer cachingTransformer = Mockito.mock(CachingResourceTransformer.class);
AppCacheManifestTransformer appCacheTransformer = Mockito.mock(AppCacheManifestTransformer.class);
CssLinkResourceTransformer cssLinkTransformer = new CssLinkResourceTransformer();
this.registration.setCacheControl(CacheControl.maxAge(3600, TimeUnit.MILLISECONDS))
.resourceChain(false)
.addResolver(cachingResolver)
.addResolver(versionResolver)
.addResolver(webjarsResolver)
.addResolver(pathResourceResolver)
.addTransformer(cachingTransformer)
.addTransformer(appCacheTransformer)
.addTransformer(cssLinkTransformer);
ResourceWebHandler handler = getHandler("/resources/**");
List<ResourceResolver> resolvers = handler.getResourceResolvers();
assertThat(resolvers.toString(), resolvers, Matchers.hasSize(4));
assertThat(resolvers.get(0), Matchers.sameInstance(cachingResolver));
assertThat(resolvers.get(1), Matchers.sameInstance(versionResolver));
assertThat(resolvers.get(2), Matchers.sameInstance(webjarsResolver));
assertThat(resolvers.get(3), Matchers.sameInstance(pathResourceResolver));
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers, Matchers.hasSize(3));
assertThat(transformers.get(0), Matchers.sameInstance(cachingTransformer));
assertThat(transformers.get(1), Matchers.sameInstance(appCacheTransformer));
assertThat(transformers.get(2), Matchers.sameInstance(cssLinkTransformer));
}
private ResourceWebHandler getHandler(String pathPattern) {
SimpleUrlHandlerMapping mapping = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping();
return (ResourceWebHandler) mapping.getUrlMap().get(pathPattern);
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.web.reactive.config;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.Ordered;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.reactive.result.view.HttpMessageWriterView;
import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
import org.springframework.web.reactive.result.view.View;
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer;
import static org.junit.Assert.*;
/**
* Unit tests for {@link ViewResolverRegistry}.
*
* @author Rossen Stoyanchev
*/
public class ViewResolverRegistryTests {
private ViewResolverRegistry registry;
@Before
public void setUp() {
StaticWebApplicationContext context = new StaticWebApplicationContext();
context.registerSingleton("freeMarkerConfigurer", FreeMarkerConfigurer.class);
this.registry = new ViewResolverRegistry(context);
}
@Test
public void order() {
assertEquals(Ordered.LOWEST_PRECEDENCE, this.registry.getOrder());
}
@Test
public void hasRegistrations() {
assertFalse(this.registry.hasRegistrations());
this.registry.freeMarker();
assertTrue(this.registry.hasRegistrations());
}
@Test
public void noResolvers() {
assertNotNull(this.registry.getViewResolvers());
assertEquals(0, this.registry.getViewResolvers().size());
assertFalse(this.registry.hasRegistrations());
}
@Test
public void customViewResolver() {
UrlBasedViewResolver viewResolver = new UrlBasedViewResolver();
this.registry.viewResolver(viewResolver);
assertSame(viewResolver, this.registry.getViewResolvers().get(0));
assertEquals(1, this.registry.getViewResolvers().size());
}
@Test
public void defaultViews() throws Exception {
View view = new HttpMessageWriterView(new Jackson2JsonEncoder());
this.registry.defaultViews(view);
assertEquals(1, this.registry.getDefaultViews().size());
assertSame(view, this.registry.getDefaultViews().get(0));
}
}

View File

@@ -0,0 +1,369 @@
/*
* 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.config;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
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.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.validation.Validator;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebExchangeDataBinder;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler;
import org.springframework.web.reactive.result.view.HttpMessageWriterView;
import org.springframework.web.reactive.result.view.View;
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.MediaType.APPLICATION_JSON;
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;
/**
* Unit tests for {@link WebReactiveConfigurationSupport}.
* @author Rossen Stoyanchev
*/
public class WebReactiveConfigurationSupportTests {
private MockServerHttpRequest request;
@Before
public void setUp() throws Exception {
this.request = MockServerHttpRequest.get("/").build();
}
@Test
public void requestMappingHandlerMapping() throws Exception {
ApplicationContext context = loadConfig(WebReactiveConfig.class);
String name = "requestMappingHandlerMapping";
RequestMappingHandlerMapping mapping = context.getBean(name, RequestMappingHandlerMapping.class);
assertNotNull(mapping);
assertEquals(0, mapping.getOrder());
assertTrue(mapping.useSuffixPatternMatch());
assertTrue(mapping.useTrailingSlashMatch());
assertTrue(mapping.useRegisteredSuffixPatternMatch());
name = "webReactiveContentTypeResolver";
RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class);
assertSame(resolver, mapping.getContentTypeResolver());
this.request = MockServerHttpRequest.get("/path.json").build();
List<MediaType> list = Collections.singletonList(MediaType.APPLICATION_JSON);
assertEquals(list, resolver.resolveMediaTypes(createExchange()));
this.request = MockServerHttpRequest.get("/path.xml").build();
assertEquals(Collections.emptyList(), resolver.resolveMediaTypes(createExchange()));
}
@Test
public void customPathMatchConfig() throws Exception {
ApplicationContext context = loadConfig(CustomPatchMatchConfig.class);
String name = "requestMappingHandlerMapping";
RequestMappingHandlerMapping mapping = context.getBean(name, RequestMappingHandlerMapping.class);
assertNotNull(mapping);
assertFalse(mapping.useSuffixPatternMatch());
assertFalse(mapping.useTrailingSlashMatch());
}
@Test
public void requestMappingHandlerAdapter() throws Exception {
ApplicationContext context = loadConfig(WebReactiveConfig.class);
String name = "requestMappingHandlerAdapter";
RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class);
assertNotNull(adapter);
List<HttpMessageReader<?>> readers = adapter.getMessageReaders();
assertEquals(7, readers.size());
assertHasMessageReader(readers, byte[].class, APPLICATION_OCTET_STREAM);
assertHasMessageReader(readers, ByteBuffer.class, APPLICATION_OCTET_STREAM);
assertHasMessageReader(readers, String.class, TEXT_PLAIN);
assertHasMessageReader(readers, Resource.class, IMAGE_PNG);
assertHasMessageReader(readers, TestBean.class, APPLICATION_XML);
assertHasMessageReader(readers, TestBean.class, APPLICATION_JSON);
assertHasMessageReader(readers, TestBean.class, null);
WebBindingInitializer bindingInitializer = adapter.getWebBindingInitializer();
assertNotNull(bindingInitializer);
WebExchangeDataBinder binder = new WebExchangeDataBinder(new Object());
bindingInitializer.initBinder(binder);
name = "webReactiveConversionService";
ConversionService service = context.getBean(name, ConversionService.class);
assertSame(service, binder.getConversionService());
name = "webReactiveValidator";
Validator validator = context.getBean(name, Validator.class);
assertSame(validator, binder.getValidator());
}
@Test
public void customMessageConverterConfig() throws Exception {
ApplicationContext context = loadConfig(CustomMessageConverterConfig.class);
String name = "requestMappingHandlerAdapter";
RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class);
assertNotNull(adapter);
List<HttpMessageReader<?>> messageReaders = adapter.getMessageReaders();
assertEquals(2, messageReaders.size());
assertHasMessageReader(messageReaders, String.class, TEXT_PLAIN);
assertHasMessageReader(messageReaders, TestBean.class, APPLICATION_XML);
}
@Test
public void responseEntityResultHandler() throws Exception {
ApplicationContext context = loadConfig(WebReactiveConfig.class);
String name = "responseEntityResultHandler";
ResponseEntityResultHandler handler = context.getBean(name, ResponseEntityResultHandler.class);
assertNotNull(handler);
assertEquals(0, handler.getOrder());
List<HttpMessageWriter<?>> writers = handler.getMessageWriters();
assertEquals(8, writers.size());
assertHasMessageWriter(writers, byte[].class, APPLICATION_OCTET_STREAM);
assertHasMessageWriter(writers, ByteBuffer.class, APPLICATION_OCTET_STREAM);
assertHasMessageWriter(writers, String.class, TEXT_PLAIN);
assertHasMessageWriter(writers, Resource.class, IMAGE_PNG);
assertHasMessageWriter(writers, TestBean.class, APPLICATION_XML);
assertHasMessageWriter(writers, TestBean.class, APPLICATION_JSON);
assertHasMessageWriter(writers, TestBean.class, MediaType.parseMediaType("text/event-stream"));
name = "webReactiveContentTypeResolver";
RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class);
assertSame(resolver, handler.getContentTypeResolver());
}
@Test
public void responseBodyResultHandler() throws Exception {
ApplicationContext context = loadConfig(WebReactiveConfig.class);
String name = "responseBodyResultHandler";
ResponseBodyResultHandler handler = context.getBean(name, ResponseBodyResultHandler.class);
assertNotNull(handler);
assertEquals(100, handler.getOrder());
List<HttpMessageWriter<?>> writers = handler.getMessageWriters();
assertEquals(8, writers.size());
assertHasMessageWriter(writers, byte[].class, APPLICATION_OCTET_STREAM);
assertHasMessageWriter(writers, ByteBuffer.class, APPLICATION_OCTET_STREAM);
assertHasMessageWriter(writers, String.class, TEXT_PLAIN);
assertHasMessageWriter(writers, Resource.class, IMAGE_PNG);
assertHasMessageWriter(writers, TestBean.class, APPLICATION_XML);
assertHasMessageWriter(writers, TestBean.class, APPLICATION_JSON);
assertHasMessageWriter(writers, TestBean.class, null);
name = "webReactiveContentTypeResolver";
RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class);
assertSame(resolver, handler.getContentTypeResolver());
}
@Test
public void viewResolutionResultHandler() throws Exception {
ApplicationContext context = loadConfig(CustomViewResolverConfig.class);
String name = "viewResolutionResultHandler";
ViewResolutionResultHandler handler = context.getBean(name, ViewResolutionResultHandler.class);
assertNotNull(handler);
assertEquals(Ordered.LOWEST_PRECEDENCE, handler.getOrder());
List<ViewResolver> resolvers = handler.getViewResolvers();
assertEquals(1, resolvers.size());
assertEquals(FreeMarkerViewResolver.class, resolvers.get(0).getClass());
List<View> views = handler.getDefaultViews();
assertEquals(1, views.size());
MimeType type = MimeTypeUtils.parseMimeType("application/json;charset=UTF-8");
assertEquals(type, views.get(0).getSupportedMediaTypes().get(0));
}
@Test
public void resourceHandler() throws Exception {
ApplicationContext context = loadConfig(CustomResourceHandlingConfig.class);
String name = "resourceHandlerMapping";
AbstractHandlerMapping handlerMapping = context.getBean(name, AbstractHandlerMapping.class);
assertNotNull(handlerMapping);
assertEquals(Ordered.LOWEST_PRECEDENCE - 1, handlerMapping.getOrder());
assertNotNull(handlerMapping.getPathHelper());
assertNotNull(handlerMapping.getPathMatcher());
SimpleUrlHandlerMapping urlHandlerMapping = (SimpleUrlHandlerMapping) handlerMapping;
WebHandler webHandler = (WebHandler) urlHandlerMapping.getUrlMap().get("/images/**");
assertNotNull(webHandler);
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
private void assertHasMessageReader(List<HttpMessageReader<?>> readers, Class<?> clazz, MediaType mediaType) {
ResolvableType type = ResolvableType.forClass(clazz);
assertTrue(readers.stream()
.filter(c -> mediaType == null || c.canRead(type, mediaType))
.findAny()
.isPresent());
}
private void assertHasMessageWriter(List<HttpMessageWriter<?>> writers, Class<?> clazz, MediaType mediaType) {
ResolvableType type = ResolvableType.forClass(clazz);
assertTrue(writers.stream()
.filter(c -> mediaType == null || c.canWrite(type, mediaType))
.findAny()
.isPresent());
}
private ApplicationContext loadConfig(Class<?>... configurationClasses) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(configurationClasses);
context.refresh();
return context;
}
@EnableWebReactive
static class WebReactiveConfig {
}
@Configuration
static class CustomPatchMatchConfig extends WebReactiveConfigurationSupport {
@Override
public void configurePathMatching(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
configurer.setUseTrailingSlashMatch(false);
}
}
@Configuration
static class CustomMessageConverterConfig extends WebReactiveConfigurationSupport {
@Override
protected void configureMessageReaders(List<HttpMessageReader<?>> messageReaders) {
messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder()));
}
@Override
protected void configureMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
}
@Override
protected void extendMessageReaders(List<HttpMessageReader<?>> messageReaders) {
messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
}
@Override
protected void extendMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
}
}
@Configuration
@SuppressWarnings("unused")
static class CustomViewResolverConfig extends WebReactiveConfigurationSupport {
@Override
protected void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
registry.defaultViews(new HttpMessageWriterView(new Jackson2JsonEncoder()));
}
@Bean
public FreeMarkerConfigurer freeMarkerConfig() {
return new FreeMarkerConfigurer();
}
}
@Configuration
static class CustomResourceHandlingConfig extends WebReactiveConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**").addResourceLocations("/images/");
}
}
@XmlRootElement
static class TestBean {
}
}

View File

@@ -0,0 +1,292 @@
/*
* 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.function;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.fasterxml.jackson.annotation.JsonView;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.codec.ByteBufferDecoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.FormHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.springframework.http.codec.json.AbstractJackson2Codec.JSON_VIEW_HINT;
/**
* @author Arjen Poutsma
* @author Sebastien Deleuze
*/
public class BodyExtractorsTests {
private BodyExtractor.Context context;
private Map<String, Object> hints;
@Before
public void createContext() {
final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder()));
messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
messageReaders.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
messageReaders.add(new FormHttpMessageReader());
this.context = new BodyExtractor.Context() {
@Override
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
return messageReaders::stream;
}
@Override
public Map<String, Object> hints() {
return hints;
}
};
this.hints = new HashMap<String, Object>();
}
@Test
public void toMono() throws Exception {
BodyExtractor<Mono<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toMono(String.class);
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
MockServerHttpRequest request = MockServerHttpRequest.post("/").body(body);
Mono<String> result = extractor.extract(request, this.context);
StepVerifier.create(result)
.expectNext("foo")
.expectComplete()
.verify();
}
@Test
public void toMonoWithHints() throws Exception {
BodyExtractor<Mono<User>, ReactiveHttpInputMessage> extractor = BodyExtractors.toMono(User.class);
this.hints.put(JSON_VIEW_HINT, SafeToDeserialize.class);
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("{\"username\":\"foo\",\"password\":\"bar\"}".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
MockServerHttpRequest request = MockServerHttpRequest.post("/")
.contentType(MediaType.APPLICATION_JSON)
.body(body);
Mono<User> result = extractor.extract(request, this.context);
StepVerifier.create(result)
.consumeNextWith(user -> {
assertEquals("foo", user.getUsername());
assertNull(user.getPassword());
})
.expectComplete()
.verify();
}
@Test
public void toFlux() throws Exception {
BodyExtractor<Flux<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toFlux(String.class);
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
MockServerHttpRequest request = MockServerHttpRequest.post("/").body(body);
Flux<String> result = extractor.extract(request, this.context);
StepVerifier.create(result)
.expectNext("foo")
.expectComplete()
.verify();
}
@Test
public void toFluxWithHints() throws Exception {
BodyExtractor<Flux<User>, ReactiveHttpInputMessage> extractor = BodyExtractors.toFlux(User.class);
this.hints.put(JSON_VIEW_HINT, SafeToDeserialize.class);
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("[{\"username\":\"foo\",\"password\":\"bar\"},{\"username\":\"bar\",\"password\":\"baz\"}]".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
MockServerHttpRequest request = MockServerHttpRequest.post("/")
.contentType(MediaType.APPLICATION_JSON)
.body(body);
Flux<User> result = extractor.extract(request, this.context);
StepVerifier.create(result)
.consumeNextWith(user -> {
assertEquals("foo", user.getUsername());
assertNull(user.getPassword());
})
.consumeNextWith(user -> {
assertEquals("bar", user.getUsername());
assertNull(user.getPassword());
})
.expectComplete()
.verify();
}
@Test
public void toFluxUnacceptable() throws Exception {
BodyExtractor<Flux<String>, ReactiveHttpInputMessage> extractor = BodyExtractors.toFlux(String.class);
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
MockServerHttpRequest request = MockServerHttpRequest.post("/")
.contentType(MediaType.APPLICATION_JSON)
.body(body);
BodyExtractor.Context emptyContext = new BodyExtractor.Context() {
@Override
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
return Stream::empty;
}
@Override
public Map<String, Object> hints() {
return Collections.emptyMap();
}
};
Flux<String> result = extractor.extract(request, emptyContext);
StepVerifier.create(result)
.expectError(UnsupportedMediaTypeException.class)
.verify();
}
@Test
public void toFormData() throws Exception {
BodyExtractor<Mono<MultiValueMap<String, String>>, ServerHttpRequest> extractor = BodyExtractors.toFormData();
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
MockServerHttpRequest request = MockServerHttpRequest.post("/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(body);
Mono<MultiValueMap<String, String>> result = extractor.extract(request, this.context);
StepVerifier.create(result)
.consumeNextWith(form -> {
assertEquals("Invalid result", 3, form.size());
assertEquals("Invalid result", "value 1", form.getFirst("name 1"));
List<String> values = form.get("name 2");
assertEquals("Invalid result", 2, values.size());
assertEquals("Invalid result", "value 2+1", values.get(0));
assertEquals("Invalid result", "value 2+2", values.get(1));
assertNull("Invalid result", form.getFirst("name 3"));
})
.expectComplete()
.verify();
}
@Test
public void toDataBuffers() throws Exception {
BodyExtractor<Flux<DataBuffer>, ReactiveHttpInputMessage> extractor = BodyExtractors.toDataBuffers();
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
MockServerHttpRequest request = MockServerHttpRequest.post("/").body(body);
Flux<DataBuffer> result = extractor.extract(request, this.context);
StepVerifier.create(result)
.expectNext(dataBuffer)
.expectComplete()
.verify();
}
interface SafeToDeserialize {}
@SuppressWarnings("unused")
private static class User {
@JsonView(SafeToDeserialize.class)
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
}

View File

@@ -0,0 +1,291 @@
/*
* 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.function;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.fasterxml.jackson.annotation.JsonView;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.FormHttpMessageWriter;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.http.codec.ServerSentEventHttpMessageWriter;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.client.reactive.test.MockClientHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertArrayEquals;
import static org.springframework.http.codec.json.AbstractJackson2Codec.JSON_VIEW_HINT;
/**
* @author Arjen Poutsma
* @author Sebastien Deleuze
*/
public class BodyInsertersTests {
private BodyInserter.Context context;
private Map<String, Object> hints;
@Before
public void createContext() {
final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
messageWriters.add(new ResourceHttpMessageWriter());
messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
Jackson2JsonEncoder jsonEncoder = new Jackson2JsonEncoder();
messageWriters.add(new EncoderHttpMessageWriter<>(jsonEncoder));
messageWriters
.add(new ServerSentEventHttpMessageWriter(Collections.singletonList(jsonEncoder)));
messageWriters.add(new FormHttpMessageWriter());
this.context = new BodyInserter.Context() {
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return messageWriters::stream;
}
@Override
public Map<String, Object> hints() {
return hints;
}
};
this.hints = new HashMap();
}
@Test
public void ofString() throws Exception {
String body = "foo";
BodyInserter<String, ReactiveHttpOutputMessage> inserter = BodyInserters.fromObject(body);
MockServerHttpResponse response = new MockServerHttpResponse();
Mono<Void> result = inserter.insert(response, this.context);
StepVerifier.create(result).expectComplete().verify();
ByteBuffer byteBuffer = ByteBuffer.wrap(body.getBytes(UTF_8));
DataBuffer buffer = new DefaultDataBufferFactory().wrap(byteBuffer);
StepVerifier.create(response.getBody())
.expectNext(buffer)
.expectComplete()
.verify();
}
@Test
public void ofObject() throws Exception {
User body = new User("foo", "bar");
BodyInserter<User, ReactiveHttpOutputMessage> inserter = BodyInserters.fromObject(body);
MockServerHttpResponse response = new MockServerHttpResponse();
Mono<Void> result = inserter.insert(response, this.context);
StepVerifier.create(result).expectComplete().verify();
StepVerifier.create(response.getBodyAsString())
.expectNext("{\"username\":\"foo\",\"password\":\"bar\"}")
.expectComplete()
.verify();
}
@Test
public void ofObjectWithHints() throws Exception {
User body = new User("foo", "bar");
BodyInserter<User, ReactiveHttpOutputMessage> inserter = BodyInserters.fromObject(body);
this.hints.put(JSON_VIEW_HINT, SafeToSerialize.class);
MockServerHttpResponse response = new MockServerHttpResponse();
Mono<Void> result = inserter.insert(response, this.context);
StepVerifier.create(result).expectComplete().verify();
StepVerifier.create(response.getBodyAsString())
.expectNext("{\"username\":\"foo\"}")
.expectComplete()
.verify();
}
@Test
public void ofPublisher() throws Exception {
Flux<String> body = Flux.just("foo");
BodyInserter<Flux<String>, ReactiveHttpOutputMessage> inserter = BodyInserters.fromPublisher(body, String.class);
MockServerHttpResponse response = new MockServerHttpResponse();
Mono<Void> result = inserter.insert(response, this.context);
StepVerifier.create(result).expectComplete().verify();
ByteBuffer byteBuffer = ByteBuffer.wrap("foo".getBytes(UTF_8));
DataBuffer buffer = new DefaultDataBufferFactory().wrap(byteBuffer);
StepVerifier.create(response.getBody())
.expectNext(buffer)
.expectComplete()
.verify();
}
@Test
public void ofResource() throws Exception {
Resource body = new ClassPathResource("response.txt", getClass());
BodyInserter<Resource, ReactiveHttpOutputMessage> inserter = BodyInserters.fromResource(body);
MockServerHttpResponse response = new MockServerHttpResponse();
Mono<Void> result = inserter.insert(response, this.context);
StepVerifier.create(result).expectComplete().verify();
byte[] expectedBytes = Files.readAllBytes(body.getFile().toPath());
StepVerifier.create(response.getBody())
.consumeNextWith(dataBuffer -> {
byte[] resultBytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(resultBytes);
assertArrayEquals(expectedBytes, resultBytes);
})
.expectComplete()
.verify();
}
@Test
public void ofServerSentEventFlux() throws Exception {
ServerSentEvent<String> event = ServerSentEvent.builder("foo").build();
Flux<ServerSentEvent<String>> body = Flux.just(event);
BodyInserter<Flux<ServerSentEvent<String>>, ServerHttpResponse> inserter =
BodyInserters.fromServerSentEvents(body);
MockServerHttpResponse response = new MockServerHttpResponse();
Mono<Void> result = inserter.insert(response, this.context);
StepVerifier.create(result).expectNextCount(0).expectComplete().verify();
}
@Test
public void ofServerSentEventClass() throws Exception {
Flux<String> body = Flux.just("foo");
BodyInserter<Flux<String>, ServerHttpResponse> inserter =
BodyInserters.fromServerSentEvents(body, String.class);
MockServerHttpResponse response = new MockServerHttpResponse();
Mono<Void> result = inserter.insert(response, this.context);
StepVerifier.create(result).expectNextCount(0).expectComplete().verify();
}
@Test
public void ofFormData() throws Exception {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.set("name 1", "value 1");
body.add("name 2", "value 2+1");
body.add("name 2", "value 2+2");
body.add("name 3", null);
BodyInserter<MultiValueMap<String, String>, ClientHttpRequest>
inserter = BodyInserters.fromFormData(body);
MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, URI.create("http://example.com"));
Mono<Void> result = inserter.insert(request, this.context);
StepVerifier.create(result).expectComplete().verify();
StepVerifier.create(request.getBody())
.consumeNextWith(dataBuffer -> {
byte[] resultBytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(resultBytes);
assertArrayEquals("name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3".getBytes(StandardCharsets.UTF_8),
resultBytes);
})
.expectComplete()
.verify();
}
@Test
public void ofDataBuffers() throws Exception {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
BodyInserter<Flux<DataBuffer>, ReactiveHttpOutputMessage> inserter = BodyInserters.fromDataBuffers(body);
MockServerHttpResponse response = new MockServerHttpResponse();
Mono<Void> result = inserter.insert(response, this.context);
StepVerifier.create(result).expectComplete().verify();
StepVerifier.create(response.getBody())
.expectNext(dataBuffer)
.expectComplete()
.verify();
}
interface SafeToSerialize {}
private static class User {
@JsonView(SafeToSerialize.class)
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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.function.client;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.mock.http.client.reactive.test.MockClientHttpRequest;
import org.springframework.web.reactive.function.BodyInserter;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.http.HttpMethod.DELETE;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;
/**
* @author Arjen Poutsma
*/
public class DefaultClientRequestBuilderTests {
@Test
public void from() throws Exception {
ClientRequest<Void> other = ClientRequest.method(GET, URI.create("http://example.com"))
.header("foo", "bar")
.cookie("baz", "qux").build();
ClientRequest<Void> result = ClientRequest.from(other).build();
assertEquals(new URI("http://example.com"), result.url());
assertEquals(GET, result.method());
assertEquals("bar", result.headers().getFirst("foo"));
assertEquals("qux", result.cookies().getFirst("baz"));
}
@Test
public void method() throws Exception {
URI url = new URI("http://example.com");
ClientRequest<Void> result = ClientRequest.method(DELETE, url).build();
assertEquals(url, result.url());
assertEquals(DELETE, result.method());
}
@Test
public void cookie() throws Exception {
ClientRequest<Void> result = ClientRequest.method(GET, URI.create("http://example.com"))
.cookie("foo", "bar").build();
assertEquals("bar", result.cookies().getFirst("foo"));
}
@Test
public void build() throws Exception {
ClientRequest<Void> result = ClientRequest.method(GET, URI.create("http://example.com"))
.header("MyKey", "MyValue")
.cookie("foo", "bar")
.build();
MockClientHttpRequest request = new MockClientHttpRequest(GET, "/");
ExchangeStrategies strategies = mock(ExchangeStrategies.class);
result.writeTo(request, strategies).block();
assertEquals("MyValue", request.getHeaders().getFirst("MyKey"));
assertEquals("bar", request.getCookies().getFirst("foo").getValue());
assertNull(request.getBody());
}
@Test
public void bodyInserter() throws Exception {
String body = "foo";
BodyInserter<String, ClientHttpRequest> inserter =
(response, strategies) -> {
byte[] bodyBytes = body.getBytes(UTF_8);
ByteBuffer byteBuffer = ByteBuffer.wrap(bodyBytes);
DataBuffer buffer = new DefaultDataBufferFactory().wrap(byteBuffer);
return response.writeWith(Mono.just(buffer));
};
ClientRequest<String> result = ClientRequest.method(POST, URI.create("http://example.com"))
.body(inserter);
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
ExchangeStrategies strategies = mock(ExchangeStrategies.class);
when(strategies.messageWriters()).thenReturn(messageWriters::stream);
MockClientHttpRequest request = new MockClientHttpRequest(GET, "/");
result.writeTo(request, strategies).block();
assertNotNull(request.getBody());
}
}

View File

@@ -0,0 +1,198 @@
/*
* 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.function.client;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRange;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
/**
* @author Arjen Poutsma
*/
public class DefaultClientResponseTests {
private ClientHttpResponse mockResponse;
private ExchangeStrategies mockExchangeStrategies;
private DefaultClientResponse defaultClientResponse;
@Before
public void createMocks() {
mockResponse = mock(ClientHttpResponse.class);
mockExchangeStrategies = mock(ExchangeStrategies.class);
defaultClientResponse = new DefaultClientResponse(mockResponse, mockExchangeStrategies);
}
@Test
public void statusCode() throws Exception {
HttpStatus status = HttpStatus.CONTINUE;
when(mockResponse.getStatusCode()).thenReturn(status);
assertEquals(status, defaultClientResponse.statusCode());
}
@Test
public void header() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
long contentLength = 42L;
httpHeaders.setContentLength(contentLength);
MediaType contentType = MediaType.TEXT_PLAIN;
httpHeaders.setContentType(contentType);
InetSocketAddress host = InetSocketAddress.createUnresolved("localhost", 80);
httpHeaders.setHost(host);
List<HttpRange> range = Collections.singletonList(HttpRange.createByteRange(0, 42));
httpHeaders.setRange(range);
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
ClientResponse.Headers headers = defaultClientResponse.headers();
assertEquals(OptionalLong.of(contentLength), headers.contentLength());
assertEquals(Optional.of(contentType), headers.contentType());
assertEquals(httpHeaders, headers.asHttpHeaders());
}
@Test
public void body() throws Exception {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultClientResponse.body(toMono(String.class));
assertEquals("foo", resultMono.block());
}
@Test
public void bodyToMono() throws Exception {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultClientResponse.bodyToMono(String.class);
assertEquals("foo", resultMono.block());
}
@Test
public void bodyToMonoError() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.NOT_FOUND);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultClientResponse.bodyToMono(String.class);
StepVerifier.create(resultMono)
.expectError(WebClientException.class)
.verify();
}
@Test
public void bodyToFlux() throws Exception {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
when(mockResponse.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Flux<String> resultFlux = defaultClientResponse.bodyToFlux(String.class);
Mono<List<String>> result = resultFlux.collectList();
assertEquals(Collections.singletonList("foo"), result.block());
}
@Test
public void bodyToFluxError() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockResponse.getHeaders()).thenReturn(httpHeaders);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.INTERNAL_SERVER_ERROR);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockExchangeStrategies.messageReaders()).thenReturn(messageReaders::stream);
Flux<String> resultFlux = defaultClientResponse.bodyToFlux(String.class);
StepVerifier.create(resultFlux)
.expectError(WebClientException.class)
.verify();
}
}

View File

@@ -0,0 +1,112 @@
/*
* 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.function.client;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link DefaultWebClient}.
* @author Rossen Stoyanchev
*/
public class DefaultWebClientTests {
private ExchangeFunction exchangeFunction;
@Captor
private ArgumentCaptor<ClientRequest<?>> captor;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
this.exchangeFunction = mock(ExchangeFunction.class);
when(this.exchangeFunction.exchange(captor.capture())).thenReturn(Mono.empty());
}
@Test
public void basic() throws Exception {
WebClient client = builder().build();
client.get().uri("/path").exchange();
ClientRequest<?> request = verifyExchange();
assertEquals("/base/path", request.url().toString());
assertEquals(new HttpHeaders(), request.headers());
assertEquals(Collections.emptyMap(), request.cookies());
}
@Test
public void requestHeaderAndCookie() throws Exception {
WebClient client = builder().build();
client.get().uri("/path").accept(MediaType.APPLICATION_JSON).cookie("id", "123").exchange();
ClientRequest<?> request = verifyExchange();
assertEquals("application/json", request.headers().getFirst("Accept"));
assertEquals("123", request.cookies().getFirst("id"));
verifyNoMoreInteractions(this.exchangeFunction);
}
@Test
public void defaultHeaderAndCookie() throws Exception {
WebClient client = builder().defaultHeader("Accept", "application/json").defaultCookie("id", "123").build();
client.get().uri("/path").exchange();
ClientRequest<?> request = verifyExchange();
assertEquals("application/json", request.headers().getFirst("Accept"));
assertEquals("123", request.cookies().getFirst("id"));
verifyNoMoreInteractions(this.exchangeFunction);
}
@Test
public void defaultHeaderAndCookieOverrides() throws Exception {
WebClient client = builder().defaultHeader("Accept", "application/json").defaultCookie("id", "123").build();
client.get().uri("/path").header("Accept", "application/xml").cookie("id", "456").exchange();
ClientRequest<?> request = verifyExchange();
assertEquals("application/xml", request.headers().getFirst("Accept"));
assertEquals("456", request.cookies().getFirst("id"));
verifyNoMoreInteractions(this.exchangeFunction);
}
private WebClient.Builder builder() {
return WebClient.builder("/base").exchangeFunction(this.exchangeFunction);
}
private ClientRequest<?> verifyExchange() {
ClientRequest<?> request = this.captor.getValue();
Mockito.verify(this.exchangeFunction).exchange(request);
verifyNoMoreInteractions(this.exchangeFunction);
return request;
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.web.reactive.function.client;
import java.net.URI;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.springframework.http.HttpMethod.GET;
/**
* @author Arjen Poutsma
*/
public class ExchangeFilterFunctionsTests {
@Test
public void andThen() throws Exception {
ClientRequest<Void> request = ClientRequest.method(GET, URI.create("http://example.com")).build();
ClientResponse response = mock(ClientResponse.class);
ExchangeFunction exchange = r -> Mono.just(response);
boolean[] filtersInvoked = new boolean[2];
ExchangeFilterFunction filter1 = (r, n) -> {
assertFalse(filtersInvoked[0]);
assertFalse(filtersInvoked[1]);
filtersInvoked[0] = true;
assertFalse(filtersInvoked[1]);
return n.exchange(r);
};
ExchangeFilterFunction filter2 = (r, n) -> {
assertTrue(filtersInvoked[0]);
assertFalse(filtersInvoked[1]);
filtersInvoked[1] = true;
return n.exchange(r);
};
ExchangeFilterFunction filter = filter1.andThen(filter2);
ClientResponse result = filter.filter(request, exchange).block();
assertEquals(response, result);
assertTrue(filtersInvoked[0]);
assertTrue(filtersInvoked[1]);
}
@Test
public void apply() throws Exception {
ClientRequest<Void> request = ClientRequest.method(GET, URI.create("http://example.com")).build();
ClientResponse response = mock(ClientResponse.class);
ExchangeFunction exchange = r -> Mono.just(response);
boolean[] filterInvoked = new boolean[1];
ExchangeFilterFunction filter = (r, n) -> {
assertFalse(filterInvoked[0]);
filterInvoked[0] = true;
return n.exchange(r);
};
ExchangeFunction filteredExchange = filter.apply(exchange);
ClientResponse result = filteredExchange.exchange(request).block();
assertEquals(response, result);
assertTrue(filterInvoked[0]);
}
@Test
public void basicAuthentication() throws Exception {
ClientRequest<Void> request = ClientRequest.method(GET, URI.create("http://example.com")).build();
ClientResponse response = mock(ClientResponse.class);
ExchangeFunction exchange = r -> {
assertTrue(r.headers().containsKey(HttpHeaders.AUTHORIZATION));
assertTrue(r.headers().getFirst(HttpHeaders.AUTHORIZATION).startsWith("Basic "));
return Mono.just(response);
};
ExchangeFilterFunction auth = ExchangeFilterFunctions.basicAuthentication("foo", "bar");
assertFalse(request.headers().containsKey(HttpHeaders.AUTHORIZATION));
ClientResponse result = auth.filter(request, exchange).block();
assertEquals(response, result);
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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.function.client;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Arjen Poutsma
*/
public class ExchangeStrategiesTests {
@Test
public void empty() {
ExchangeStrategies strategies = ExchangeStrategies.empty().build();
assertEquals(Optional.empty(), strategies.messageReaders().get().findFirst());
assertEquals(Optional.empty(), strategies.messageWriters().get().findFirst());
}
@Test
public void ofSuppliers() {
HttpMessageReader<?> messageReader = new DummyMessageReader();
HttpMessageWriter<?> messageWriter = new DummyMessageWriter();
ExchangeStrategies strategies = ExchangeStrategies.of(
() -> Stream.of(messageReader),
() -> Stream.of(messageWriter));
assertEquals(1L, strategies.messageReaders().get().collect(Collectors.counting()).longValue());
assertEquals(Optional.of(messageReader), strategies.messageReaders().get().findFirst());
assertEquals(1L, strategies.messageWriters().get().collect(Collectors.counting()).longValue());
assertEquals(Optional.of(messageWriter), strategies.messageWriters().get().findFirst());
}
@Test
public void toConfiguration() throws Exception {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("messageWriter", DummyMessageWriter.class);
applicationContext.registerSingleton("messageReader", DummyMessageReader.class);
applicationContext.refresh();
ExchangeStrategies strategies = ExchangeStrategies.of(applicationContext);
assertTrue(strategies.messageReaders().get()
.allMatch(r -> r instanceof DummyMessageReader));
assertTrue(strategies.messageWriters().get()
.allMatch(r -> r instanceof DummyMessageWriter));
}
private static class DummyMessageWriter implements HttpMessageWriter<Object> {
@Override
public boolean canWrite(ResolvableType type, MediaType mediaType) {
return false;
}
@Override
public List<MediaType> getWritableMediaTypes() {
return Collections.emptyList();
}
@Override
public Mono<Void> write(Publisher<?> inputStream, ResolvableType type,
MediaType contentType,
ReactiveHttpOutputMessage outputMessage,
Map<String, Object> hints) {
return Mono.empty();
}
}
private static class DummyMessageReader implements HttpMessageReader<Object> {
@Override
public boolean canRead(ResolvableType type, MediaType mediaType) {
return false;
}
@Override
public List<MediaType> getReadableMediaTypes() {
return Collections.emptyList();
}
@Override
public Flux<Object> read(ResolvableType type, ReactiveHttpInputMessage inputMessage,
Map<String, Object> hints) {
return Flux.empty();
}
@Override
public Mono<Object> readMono(ResolvableType type, ReactiveHttpInputMessage inputMessage,
Map<String, Object> hints) {
return Mono.empty();
}
}
}

View File

@@ -0,0 +1,301 @@
/*
* 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.function.client;
import java.time.Duration;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.Pojo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
/**
* Integration tests using a {@link ExchangeFunction} through {@link WebClient}.
*
* @author Brian Clozel
* @author Rossen Stoyanchev
*/
public class WebClientIntegrationTests {
private MockWebServer server;
private WebClient webClient;
@Before
public void setup() {
this.server = new MockWebServer();
String baseUrl = this.server.url("/").toString();
this.webClient = WebClient.create(baseUrl);
}
@After
public void tearDown() throws Exception {
this.server.shutdown();
}
@Test
public void headers() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
Mono<HttpHeaders> result = this.webClient.get()
.uri("/greeting?name=Spring")
.exchange()
.map(response -> response.headers().asHttpHeaders());
StepVerifier.create(result)
.consumeNextWith(
httpHeaders -> {
assertEquals(MediaType.TEXT_PLAIN, httpHeaders.getContentType());
assertEquals(13L, httpHeaders.getContentLength());
})
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("*/*", recordedRequest.getHeader(HttpHeaders.ACCEPT));
Assert.assertEquals("/greeting?name=Spring", recordedRequest.getPath());
}
@Test
public void plainText() throws Exception {
this.server.enqueue(new MockResponse().setBody("Hello Spring!"));
Mono<String> result = this.webClient.get()
.uri("/greeting?name=Spring")
.header("X-Test-Header", "testvalue")
.exchange()
.then(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext("Hello Spring!")
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("testvalue", recordedRequest.getHeader("X-Test-Header"));
Assert.assertEquals("*/*", recordedRequest.getHeader(HttpHeaders.ACCEPT));
Assert.assertEquals("/greeting?name=Spring", recordedRequest.getPath());
}
@Test
public void jsonString() throws Exception {
String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}";
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody(content));
Mono<String> result = this.webClient.get()
.uri("/json")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.then(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext(content)
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/json", recordedRequest.getPath());
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
}
@Test
public void jsonPojoMono() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody("{\"bar\":\"barbar\",\"foo\":\"foofoo\"}"));
Mono<Pojo> result = this.webClient.get()
.uri("/pojo")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.then(response -> response.bodyToMono(Pojo.class));
StepVerifier.create(result)
.consumeNextWith(p -> assertEquals("barbar", p.getBar()))
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/pojo", recordedRequest.getPath());
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
}
@Test
public void jsonPojoFlux() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
.setBody("[{\"bar\":\"bar1\",\"foo\":\"foo1\"},{\"bar\":\"bar2\",\"foo\":\"foo2\"}]"));
Flux<Pojo> result = this.webClient.get()
.uri("/pojos")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToFlux(Pojo.class));
StepVerifier.create(result)
.consumeNextWith(p -> assertThat(p.getBar(), Matchers.is("bar1")))
.consumeNextWith(p -> assertThat(p.getBar(), Matchers.is("bar2")))
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/pojos", recordedRequest.getPath());
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
}
@Test
public void postJsonPojo() throws Exception {
this.server.enqueue(new MockResponse()
.setHeader("Content-Type", "application/json")
.setBody("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"));
Mono<Pojo> result = this.webClient.post()
.uri("/pojo/capitalize")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.exchange(fromObject(new Pojo("foofoo", "barbar")))
.then(response -> response.bodyToMono(Pojo.class));
StepVerifier.create(result)
.consumeNextWith(p -> assertEquals("BARBAR", p.getBar()))
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/pojo/capitalize", recordedRequest.getPath());
Assert.assertEquals("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}", recordedRequest.getBody().readUtf8());
Assert.assertEquals("chunked", recordedRequest.getHeader(HttpHeaders.TRANSFER_ENCODING));
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.ACCEPT));
Assert.assertEquals("application/json", recordedRequest.getHeader(HttpHeaders.CONTENT_TYPE));
}
@Test
public void cookies() throws Exception {
this.server.enqueue(new MockResponse()
.setHeader("Content-Type", "text/plain").setBody("test"));
Mono<String> result = this.webClient.get()
.uri("/test")
.cookie("testkey", "testvalue")
.exchange()
.then(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext("test")
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("/test", recordedRequest.getPath());
Assert.assertEquals("testkey=testvalue", recordedRequest.getHeader(HttpHeaders.COOKIE));
}
@Test
public void notFound() throws Exception {
this.server.enqueue(new MockResponse().setResponseCode(404)
.setHeader("Content-Type", "text/plain").setBody("Not Found"));
Mono<ClientResponse> result = this.webClient.get().uri("/greeting?name=Spring").exchange();
StepVerifier.create(result)
.consumeNextWith(response -> assertEquals(HttpStatus.NOT_FOUND, response.statusCode()))
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("*/*", recordedRequest.getHeader(HttpHeaders.ACCEPT));
Assert.assertEquals("/greeting?name=Spring", recordedRequest.getPath());
}
@Test
public void buildFilter() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
WebClient filteredClient = this.webClient.filter(
(request, next) -> {
ClientRequest<?> filteredRequest = ClientRequest.from(request).header("foo", "bar").build();
return next.exchange(filteredRequest);
});
Mono<String> result = filteredClient.get()
.uri("/greeting?name=Spring")
.exchange()
.then(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext("Hello Spring!")
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("bar", recordedRequest.getHeader("foo"));
}
@Test
public void filter() throws Exception {
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
WebClient filteredClient = this.webClient.filter(
(request, next) -> {
ClientRequest<?> filteredRequest = ClientRequest.from(request).header("foo", "bar").build();
return next.exchange(filteredRequest);
});
Mono<String> result = filteredClient.get()
.uri("/greeting?name=Spring")
.exchange()
.then(response -> response.bodyToMono(String.class));
StepVerifier.create(result)
.expectNext("Hello Spring!")
.expectComplete()
.verify(Duration.ofSeconds(3));
RecordedRequest recordedRequest = server.takeRequest();
Assert.assertEquals(1, server.getRequestCount());
Assert.assertEquals("bar", recordedRequest.getHeader("foo"));
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.web.reactive.function.server;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
/**
* @author Arjen Poutsma
*/
public abstract class AbstractRouterFunctionIntegrationTests
extends AbstractHttpHandlerIntegrationTests {
@Override
protected final HttpHandler createHttpHandler() {
RouterFunction<?> routerFunction = routerFunction();
return RouterFunctions.toHttpHandler(routerFunction);
}
protected abstract RouterFunction<?> routerFunction();
}

View File

@@ -0,0 +1,261 @@
/*
* 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.function.server;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
import org.springframework.http.MediaType;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.WebSession;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
/**
* @author Arjen Poutsma
*/
public class DefaultServerRequestTests {
private ServerHttpRequest mockRequest;
private ServerHttpResponse mockResponse;
private ServerWebExchange mockExchange;
private HandlerStrategies mockHandlerStrategies;
private DefaultServerRequest defaultRequest;
@Before
public void createMocks() {
mockRequest = mock(ServerHttpRequest.class);
mockResponse = mock(ServerHttpResponse.class);
mockExchange = mock(ServerWebExchange.class);
when(mockExchange.getRequest()).thenReturn(mockRequest);
when(mockExchange.getResponse()).thenReturn(mockResponse);
mockHandlerStrategies = mock(HandlerStrategies.class);
defaultRequest = new DefaultServerRequest(mockExchange, mockHandlerStrategies);
}
@Test
public void method() throws Exception {
HttpMethod method = HttpMethod.HEAD;
when(mockRequest.getMethod()).thenReturn(method);
assertEquals(method, defaultRequest.method());
}
@Test
public void uri() throws Exception {
URI uri = URI.create("https://example.com");
when(mockRequest.getURI()).thenReturn(uri);
assertEquals(uri, defaultRequest.uri());
}
@Test
public void attribute() throws Exception {
when(mockExchange.getAttribute("foo")).thenReturn(Optional.of("bar"));
assertEquals(Optional.of("bar"), defaultRequest.attribute("foo"));
}
@Test
public void queryParams() throws Exception {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.set("foo", "bar");
when(mockRequest.getQueryParams()).thenReturn(queryParams);
assertEquals(Optional.of("bar"), defaultRequest.queryParam("foo"));
}
@Test
public void pathVariable() throws Exception {
Map<String, String> pathVariables = Collections.singletonMap("foo", "bar");
when(mockExchange.getAttribute(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE)).thenReturn(Optional.of(pathVariables));
assertEquals("bar", defaultRequest.pathVariable("foo"));
}
@Test(expected = IllegalArgumentException.class)
public void pathVariableNotFound() throws Exception {
Map<String, String> pathVariables = Collections.singletonMap("foo", "bar");
when(mockExchange.getAttribute(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE)).thenReturn(Optional.of(pathVariables));
assertEquals("bar", defaultRequest.pathVariable("baz"));
}
@Test
public void pathVariables() throws Exception {
Map<String, String> pathVariables = Collections.singletonMap("foo", "bar");
when(mockExchange.getAttribute(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE)).thenReturn(Optional.of(pathVariables));
assertEquals(pathVariables, defaultRequest.pathVariables());
}
@Test
public void session() throws Exception {
WebSession session = mock(WebSession.class);
when(mockExchange.getSession()).thenReturn(Mono.just(session));
assertEquals(session, defaultRequest.session().block());
}
@Test
public void header() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
List<MediaType> accept =
Collections.singletonList(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(accept);
List<Charset> acceptCharset = Collections.singletonList(StandardCharsets.UTF_8);
httpHeaders.setAcceptCharset(acceptCharset);
long contentLength = 42L;
httpHeaders.setContentLength(contentLength);
MediaType contentType = MediaType.TEXT_PLAIN;
httpHeaders.setContentType(contentType);
InetSocketAddress host = InetSocketAddress.createUnresolved("localhost", 80);
httpHeaders.setHost(host);
List<HttpRange> range = Collections.singletonList(HttpRange.createByteRange(0, 42));
httpHeaders.setRange(range);
when(mockRequest.getHeaders()).thenReturn(httpHeaders);
ServerRequest.Headers headers = defaultRequest.headers();
assertEquals(accept, headers.accept());
assertEquals(acceptCharset, headers.acceptCharset());
assertEquals(OptionalLong.of(contentLength), headers.contentLength());
assertEquals(Optional.of(contentType), headers.contentType());
assertEquals(httpHeaders, headers.asHttpHeaders());
}
@Test
public void body() throws Exception {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockRequest.getHeaders()).thenReturn(httpHeaders);
when(mockRequest.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockHandlerStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultRequest.body(toMono(String.class));
assertEquals("foo", resultMono.block());
}
@Test
public void bodyToMono() throws Exception {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockRequest.getHeaders()).thenReturn(httpHeaders);
when(mockRequest.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockHandlerStrategies.messageReaders()).thenReturn(messageReaders::stream);
Mono<String> resultMono = defaultRequest.bodyToMono(String.class);
assertEquals("foo", resultMono.block());
}
@Test
public void bodyToFlux() throws Exception {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockRequest.getHeaders()).thenReturn(httpHeaders);
when(mockRequest.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockHandlerStrategies.messageReaders()).thenReturn(messageReaders::stream);
Flux<String> resultFlux = defaultRequest.bodyToFlux(String.class);
Mono<List<String>> result = resultFlux.collectList();
assertEquals(Collections.singletonList("foo"), result.block());
}
@Test
public void bodyUnacceptable() throws Exception {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
when(mockRequest.getHeaders()).thenReturn(httpHeaders);
when(mockRequest.getBody()).thenReturn(body);
Set<HttpMessageReader<?>> messageReaders = Collections.emptySet();
when(mockHandlerStrategies.messageReaders()).thenReturn(messageReaders::stream);
Flux<String> resultFlux = defaultRequest.bodyToFlux(String.class);
StepVerifier.create(resultFlux)
.expectError(UnsupportedMediaTypeStatusException.class)
.verify();
}
}

View File

@@ -0,0 +1,367 @@
/*
* 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.web.reactive.function.server;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Arjen Poutsma
*/
public class DefaultServerResponseBuilderTests {
@Test
public void from() throws Exception {
ServerResponse other = ServerResponse.ok().header("foo", "bar").build().block();
Mono<ServerResponse> result = ServerResponse.from(other).build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.OK.equals(response.statusCode()) &&
"bar".equals(response.headers().getFirst("foo")))
.expectComplete()
.verify();
}
@Test
public void status() throws Exception {
Mono<ServerResponse> result = ServerResponse.status(HttpStatus.CREATED).build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.CREATED.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void ok() throws Exception {
Mono<ServerResponse> result = ServerResponse.ok().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.OK.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void created() throws Exception {
URI location = URI.create("http://example.com");
Mono<ServerResponse> result = ServerResponse.created(location).build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.CREATED.equals(response.statusCode()) &&
location.equals(response.headers().getLocation()))
.expectComplete()
.verify();
}
@Test
public void accepted() throws Exception {
Mono<ServerResponse> result = ServerResponse.accepted().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.ACCEPTED.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void noContent() throws Exception {
Mono<ServerResponse> result = ServerResponse.noContent().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.NO_CONTENT.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void badRequest() throws Exception {
Mono<ServerResponse> result = ServerResponse.badRequest().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.BAD_REQUEST.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void notFound() throws Exception {
Mono<ServerResponse> result = ServerResponse.notFound().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.NOT_FOUND.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void unprocessableEntity() throws Exception {
Mono<ServerResponse> result = ServerResponse.unprocessableEntity().build();
StepVerifier.create(result)
.expectNextMatches(response -> HttpStatus.UNPROCESSABLE_ENTITY.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void allow() throws Exception {
Mono<ServerResponse> result = ServerResponse.ok().allow(HttpMethod.GET).build();
Set<HttpMethod> expected = EnumSet.of(HttpMethod.GET);
StepVerifier.create(result)
.expectNextMatches(response -> expected.equals(response.headers().getAllow()))
.expectComplete()
.verify();
}
@Test
public void contentLength() throws Exception {
Mono<ServerResponse> result = ServerResponse.ok().contentLength(42).build();
StepVerifier.create(result)
.expectNextMatches(response -> Long.valueOf(42).equals(response.headers().getContentLength()))
.expectComplete()
.verify();
}
@Test
public void contentType() throws Exception {
Mono<ServerResponse>
result = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).build();
StepVerifier.create(result)
.expectNextMatches(response -> MediaType.APPLICATION_JSON.equals(response.headers().getContentType()))
.expectComplete()
.verify();
}
@Test
public void eTag() throws Exception {
Mono<ServerResponse> result = ServerResponse.ok().eTag("foo").build();
StepVerifier.create(result)
.expectNextMatches(response -> "\"foo\"".equals(response.headers().getETag()))
.expectComplete()
.verify();
}
@Test
public void lastModified() throws Exception {
ZonedDateTime now = ZonedDateTime.now();
Mono<ServerResponse> result = ServerResponse.ok().lastModified(now).build();
Long expected = now.toInstant().toEpochMilli() / 1000;
StepVerifier.create(result)
.expectNextMatches(response -> expected.equals(response.headers().getLastModified() / 1000))
.expectComplete()
.verify();
}
@Test
public void cacheControlTag() throws Exception {
Mono<ServerResponse>
result = ServerResponse.ok().cacheControl(CacheControl.noCache()).build();
StepVerifier.create(result)
.expectNextMatches(response -> "no-cache".equals(response.headers().getCacheControl()))
.expectComplete()
.verify();
}
@Test
public void varyBy() throws Exception {
Mono<ServerResponse> result = ServerResponse.ok().varyBy("foo").build();
List<String> expected = Collections.singletonList("foo");
StepVerifier.create(result)
.expectNextMatches(response -> expected.equals(response.headers().getVary()))
.expectComplete()
.verify();
}
@Test
public void statusCode() throws Exception {
HttpStatus statusCode = HttpStatus.ACCEPTED;
Mono<ServerResponse> result = ServerResponse.status(statusCode).build();
StepVerifier.create(result)
.expectNextMatches(response -> statusCode.equals(response.statusCode()))
.expectComplete()
.verify();
}
@Test
public void headers() throws Exception {
HttpHeaders headers = new HttpHeaders();
Mono<ServerResponse> result = ServerResponse.ok().headers(headers).build();
StepVerifier.create(result)
.expectNextMatches(response -> headers.equals(response.headers()))
.expectComplete()
.verify();
}
@Test
public void build() throws Exception {
Mono<ServerResponse>
result = ServerResponse.status(HttpStatus.CREATED).header("MyKey", "MyValue").build();
ServerWebExchange exchange = mock(ServerWebExchange.class);
MockServerHttpResponse response = new MockServerHttpResponse();
when(exchange.getResponse()).thenReturn(response);
HandlerStrategies strategies = mock(HandlerStrategies.class);
result.then(res -> res.writeTo(exchange, strategies)).block();
assertEquals(HttpStatus.CREATED, response.getStatusCode());
assertEquals("MyValue", response.getHeaders().getFirst("MyKey"));
assertNull(response.getBody());
}
@Test
public void buildVoidPublisher() throws Exception {
Mono<Void> mono = Mono.empty();
Mono<ServerResponse> result = ServerResponse.ok().build(mono);
ServerWebExchange exchange = mock(ServerWebExchange.class);
MockServerHttpResponse response = new MockServerHttpResponse();
when(exchange.getResponse()).thenReturn(response);
HandlerStrategies strategies = mock(HandlerStrategies.class);
result.then(res -> res.writeTo(exchange, strategies)).block();
assertNull(response.getBody());
}
/*
TODO: enable when ServerEntityResponse is reintroduced
@Test
public void bodyInserter() throws Exception {
String body = "foo";
Publisher<String> publisher = Mono.just(body);
BiFunction<ServerHttpResponse, BodyInserter.Context, Mono<Void>> writer =
(response, strategies) -> {
byte[] bodyBytes = body.getBytes(UTF_8);
ByteBuffer byteBuffer = ByteBuffer.wrap(bodyBytes);
DataBuffer buffer = new DefaultDataBufferFactory().wrap(byteBuffer);
return response.writeWith(Mono.just(buffer));
};
Mono<ServerResponse> result = ServerResponse.ok().body(BodyInserter.of(writer, publisher));
MockServerHttpRequest request =
new MockServerHttpRequest(HttpMethod.GET, "http://localhost");
MockServerHttpResponse mockResponse = new MockServerHttpResponse();
ServerWebExchange exchange =
new DefaultServerWebExchange(request, mockResponse, new MockWebSessionManager());
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<CharSequence>(new CharSequenceEncoder()));
HandlerStrategies strategies = mock(HandlerStrategies.class);
when(strategies.messageWriters()).thenReturn(messageWriters::stream);
StepVerifier.create(result)
.consumeNextWith(response -> {
StepVerifier.create(response.body())
.expectNext(body)
.expectComplete()
.verify();
response.writeTo(exchange, strategies);
})
.expectComplete()
.verify();
assertNotNull(mockResponse.getBody());
}
*/
/*
TODO: enable when ServerEntityResponse is reintroduced
@Test
public void render() throws Exception {
Map<String, Object> model = Collections.singletonMap("foo", "bar");
Mono<ServerResponse> result = ServerResponse.ok().render("view", model);
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, URI.create("http://localhost"));
MockServerHttpResponse mockResponse = new MockServerHttpResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(request, mockResponse, new MockWebSessionManager());
ViewResolver viewResolver = mock(ViewResolver.class);
View view = mock(View.class);
when(viewResolver.resolveViewName("view", Locale.ENGLISH)).thenReturn(Mono.just(view));
when(view.render(model, null, exchange)).thenReturn(Mono.empty());
List<ViewResolver> viewResolvers = new ArrayList<>();
viewResolvers.add(viewResolver);
HandlerStrategies mockConfig = mock(HandlerStrategies.class);
when(mockConfig.viewResolvers()).thenReturn(viewResolvers::stream);
StepVerifier.create(result)
.consumeNextWith(response -> {
StepVerifier.create(response.body())
.expectNextMatches(rendering -> "view".equals(rendering.name())
&& model.equals(rendering.model()))
.expectComplete()
.verify();
})
.expectComplete()
.verify();
}
*/
/*
TODO: enable when ServerEntityResponse is reintroduced
@Test
public void renderObjectArray() throws Exception {
Mono<ServerResponse> result =
ServerResponse.ok().render("name", this, Collections.emptyList(), "foo");
Flux<Rendering> map = result.flatMap(ServerResponse::body);
Map<String, Object> expected = new HashMap<>(2);
expected.put("defaultServerResponseBuilderTests", this);
expected.put("string", "foo");
StepVerifier.create(map)
.expectNextMatches(rendering -> expected.equals(rendering.model()))
.expectComplete()
.verify();
}
*/
}

View File

@@ -0,0 +1,222 @@
/*
* 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.web.reactive.function.server;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.HandlerAdapter;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.config.WebReactiveConfigurationSupport;
import org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter;
import org.springframework.web.reactive.function.server.support.ServerResponseResultHandler;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.junit.Assert.assertEquals;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
import static org.springframework.web.reactive.function.BodyInserters.fromPublisher;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
* Tests the use of {@link HandlerFunction} and {@link RouterFunction} in a
* {@link DispatcherHandler}.
* @author Arjen Poutsma
*/
public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private AnnotationConfigApplicationContext wac;
private RestTemplate restTemplate;
@Before
public void createRestTemplate() {
this.restTemplate = new RestTemplate();
}
@Override
protected HttpHandler createHttpHandler() {
this.wac = new AnnotationConfigApplicationContext();
this.wac.register(TestConfiguration.class);
this.wac.refresh();
DispatcherHandler webHandler = new DispatcherHandler();
webHandler.setApplicationContext(this.wac);
return WebHttpHandlerBuilder.webHandler(webHandler).build();
}
@Test
public void mono() throws Exception {
ResponseEntity<Person> result =
this.restTemplate.getForEntity("http://localhost:" + this.port + "/mono", Person.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("John", result.getBody().getName());
}
@Test
public void flux() throws Exception {
ParameterizedTypeReference<List<Person>> reference = new ParameterizedTypeReference<List<Person>>() {};
ResponseEntity<List<Person>> result =
this.restTemplate
.exchange("http://localhost:" + this.port + "/flux", HttpMethod.GET, null, reference);
assertEquals(HttpStatus.OK, result.getStatusCode());
List<Person> body = result.getBody();
assertEquals(2, body.size());
assertEquals("John", body.get(0).getName());
assertEquals("Jane", body.get(1).getName());
}
@Configuration
static class TestConfiguration extends WebReactiveConfigurationSupport {
@Bean
public PersonHandler personHandler() {
return new PersonHandler();
}
@Bean
public HandlerAdapter handlerAdapter() {
return new HandlerFunctionAdapter();
}
@Bean
public HandlerMapping handlerMapping(RouterFunction<?> routerFunction,
ApplicationContext applicationContext) {
return RouterFunctions.toHandlerMapping(routerFunction,
new HandlerStrategies() {
@Override
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
return () -> getMessageReaders().stream();
}
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return () -> getMessageWriters().stream();
}
@Override
public Supplier<Stream<ViewResolver>> viewResolvers() {
return Stream::empty;
}
});
}
@Bean
public RouterFunction<?> routerFunction() {
PersonHandler personHandler = personHandler();
return route(RequestPredicates.GET("/mono"), personHandler::mono)
.and(route(RequestPredicates.GET("/flux"), personHandler::flux));
}
@Bean
public ServerResponseResultHandler responseResultHandler() {
return new ServerResponseResultHandler();
}
}
private static class PersonHandler {
public Mono<ServerResponse> mono(ServerRequest request) {
Person person = new Person("John");
return ServerResponse.ok().body(fromObject(person));
}
public Mono<ServerResponse> flux(ServerRequest request) {
Person person1 = new Person("John");
Person person2 = new Person("Jane");
return ServerResponse.ok().body(
fromPublisher(Flux.just(person1, person2), Person.class));
}
public Mono<ServerResponse> view() {
return ServerResponse.ok().render("foo", "bar");
}
}
private static class Person {
private String name;
@SuppressWarnings("unused")
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Person
person = (Person) o;
return !(this.name != null ? !this.name.equals(person.name) : person.name != null);
}
@Override
public int hashCode() {
return this.name != null ? this.name.hashCode() : 0;
}
@Override
public String toString() {
return "Person{" +
"name='" + this.name + '\'' +
'}';
}
}
}

View File

@@ -0,0 +1,136 @@
/*
* 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.web.reactive.function.server;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Arjen Poutsma
*/
public class HandlerStrategiesTests {
@Test
public void empty() {
HandlerStrategies strategies = HandlerStrategies.empty().build();
assertEquals(Optional.empty(), strategies.messageReaders().get().findFirst());
assertEquals(Optional.empty(), strategies.messageWriters().get().findFirst());
assertEquals(Optional.empty(), strategies.viewResolvers().get().findFirst());
}
@Test
public void ofSuppliers() {
HttpMessageReader<?> messageReader = new DummyMessageReader();
HttpMessageWriter<?> messageWriter = new DummyMessageWriter();
HandlerStrategies strategies = HandlerStrategies.of(
() -> Stream.of(messageReader),
() -> Stream.of(messageWriter),
null);
assertEquals(1L, strategies.messageReaders().get().collect(Collectors.counting()).longValue());
assertEquals(Optional.of(messageReader), strategies.messageReaders().get().findFirst());
assertEquals(1L, strategies.messageWriters().get().collect(Collectors.counting()).longValue());
assertEquals(Optional.of(messageWriter), strategies.messageWriters().get().findFirst());
assertEquals(Optional.empty(), strategies.viewResolvers().get().findFirst());
}
@Test
public void toConfiguration() throws Exception {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("messageWriter", DummyMessageWriter.class);
applicationContext.registerSingleton("messageReader", DummyMessageReader.class);
applicationContext.refresh();
HandlerStrategies strategies = HandlerStrategies.of(applicationContext);
assertTrue(strategies.messageReaders().get()
.allMatch(r -> r instanceof DummyMessageReader));
assertTrue(strategies.messageWriters().get()
.allMatch(r -> r instanceof DummyMessageWriter));
}
private static class DummyMessageWriter implements HttpMessageWriter<Object> {
@Override
public boolean canWrite(ResolvableType type, MediaType mediaType) {
return false;
}
@Override
public List<MediaType> getWritableMediaTypes() {
return Collections.emptyList();
}
@Override
public Mono<Void> write(Publisher<?> inputStream, ResolvableType type,
MediaType contentType,
ReactiveHttpOutputMessage outputMessage,
Map<String, Object> hints) {
return Mono.empty();
}
}
private static class DummyMessageReader implements HttpMessageReader<Object> {
@Override
public boolean canRead(ResolvableType type, MediaType mediaType) {
return false;
}
@Override
public List<MediaType> getReadableMediaTypes() {
return Collections.emptyList();
}
@Override
public Flux<Object> read(ResolvableType type, ReactiveHttpInputMessage inputMessage,
Map<String, Object> hints) {
return Flux.empty();
}
@Override
public Mono<Object> readMono(ResolvableType type, ReactiveHttpInputMessage inputMessage,
Map<String, Object> hints) {
return Mono.empty();
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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.web.reactive.function.server;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRange;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.support.ServerRequestWrapper;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Arjen Poutsma
*/
public class HeadersWrapperTest {
private ServerRequest.Headers mockHeaders;
private ServerRequestWrapper.HeadersWrapper wrapper;
@Before
public void createWrapper() {
mockHeaders = mock(ServerRequest.Headers.class);
wrapper = new ServerRequestWrapper.HeadersWrapper(mockHeaders);
}
@Test
public void accept() throws Exception {
List<MediaType> accept = Collections.singletonList(MediaType.APPLICATION_JSON);
when(mockHeaders.accept()).thenReturn(accept);
assertSame(accept, wrapper.accept());
}
@Test
public void acceptCharset() throws Exception {
List<Charset> acceptCharset = Collections.singletonList(StandardCharsets.UTF_8);
when(mockHeaders.acceptCharset()).thenReturn(acceptCharset);
assertSame(acceptCharset, wrapper.acceptCharset());
}
@Test
public void contentLength() throws Exception {
OptionalLong contentLength = OptionalLong.of(42L);
when(mockHeaders.contentLength()).thenReturn(contentLength);
assertSame(contentLength, wrapper.contentLength());
}
@Test
public void contentType() throws Exception {
Optional<MediaType> contentType = Optional.of(MediaType.APPLICATION_JSON);
when(mockHeaders.contentType()).thenReturn(contentType);
assertSame(contentType, wrapper.contentType());
}
@Test
public void host() throws Exception {
InetSocketAddress host = InetSocketAddress.createUnresolved("example.com", 42);
when(mockHeaders.host()).thenReturn(host);
assertSame(host, wrapper.host());
}
@Test
public void range() throws Exception {
List<HttpRange> range = Collections.singletonList(HttpRange.createByteRange(42));
when(mockHeaders.range()).thenReturn(range);
assertSame(range, wrapper.range());
}
@Test
public void header() throws Exception {
String name = "foo";
List<String> value = Collections.singletonList("bar");
when(mockHeaders.header(name)).thenReturn(value);
assertSame(value, wrapper.header(name));
}
@Test
public void asHttpHeaders() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
when(mockHeaders.asHttpHeaders()).thenReturn(httpHeaders);
assertSame(httpHeaders, wrapper.asHttpHeaders());
}
}

View File

@@ -0,0 +1,361 @@
/*
* 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.function.server;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.server.WebSession;
/**
* @author Arjen Poutsma
*/
public class MockServerRequest implements ServerRequest {
private final HttpMethod method;
private final URI uri;
private final MockHeaders headers;
private final Object body;
private final Map<String, Object> attributes;
private final MultiValueMap<String, String> queryParams;
private final Map<String, String> pathVariables;
private final WebSession session;
private MockServerRequest(HttpMethod method, URI uri,
MockHeaders headers, Object body, Map<String, Object> attributes,
MultiValueMap<String, String> queryParams,
Map<String, String> pathVariables, WebSession session) {
this.method = method;
this.uri = uri;
this.headers = headers;
this.body = body;
this.attributes = attributes;
this.queryParams = queryParams;
this.pathVariables = pathVariables;
this.session = session;
}
public static Builder builder() {
return new BuilderImpl();
}
@Override
public HttpMethod method() {
return this.method;
}
@Override
public URI uri() {
return this.uri;
}
@Override
public Headers headers() {
return this.headers;
}
@Override
@SuppressWarnings("unchecked")
public <S> S body(BodyExtractor<S, ? super ServerHttpRequest> extractor){
return (S) this.body;
}
@Override
public <S> S body(BodyExtractor<S, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
return (S) this.body;
}
@Override
@SuppressWarnings("unchecked")
public <S> Mono<S> bodyToMono(Class<? extends S> elementClass) {
return (Mono<S>) this.body;
}
@Override
@SuppressWarnings("unchecked")
public <S> Flux<S> bodyToFlux(Class<? extends S> elementClass) {
return (Flux<S>) this.body;
}
@SuppressWarnings("unchecked")
@Override
public <S> Optional<S> attribute(String name) {
return Optional.ofNullable((S) this.attributes.get(name));
}
@Override
public List<String> queryParams(String name) {
return Collections.unmodifiableList(this.queryParams.get(name));
}
@Override
public Map<String, String> pathVariables() {
return Collections.unmodifiableMap(this.pathVariables);
}
@Override
public Mono<WebSession> session() {
return Mono.justOrEmpty(this.session);
}
public interface Builder {
Builder method(HttpMethod method);
Builder uri(URI uri);
Builder header(String key, String value);
Builder headers(HttpHeaders headers);
Builder attribute(String name, Object value);
Builder attributes(Map<String, Object> attributes);
Builder queryParam(String key, String value);
Builder queryParams(MultiValueMap<String, String> queryParams);
Builder pathVariable(String key, String value);
Builder pathVariables(Map<String, String> pathVariables);
Builder session(WebSession session);
MockServerRequest body(Object body);
MockServerRequest build();
}
private static class BuilderImpl implements Builder {
private HttpMethod method = HttpMethod.GET;
private URI uri = URI.create("http://localhost");
private MockHeaders headers = new MockHeaders(new HttpHeaders());
private Object body;
private Map<String, Object> attributes = new LinkedHashMap<>();
private MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
private Map<String, String> pathVariables = new LinkedHashMap<>();
private WebSession session;
@Override
public Builder method(HttpMethod method) {
Assert.notNull(method, "'method' must not be null");
this.method = method;
return this;
}
@Override
public Builder uri(URI uri) {
Assert.notNull(uri, "'uri' must not be null");
this.uri = uri;
return this;
}
@Override
public Builder header(String key, String value) {
Assert.notNull(key, "'key' must not be null");
Assert.notNull(value, "'value' must not be null");
this.headers.header(key, value);
return this;
}
@Override
public Builder headers(HttpHeaders headers) {
Assert.notNull(headers, "'headers' must not be null");
this.headers = new MockHeaders(headers);
return this;
}
@Override
public Builder attribute(String name, Object value) {
Assert.notNull(name, "'name' must not be null");
Assert.notNull(value, "'value' must not be null");
this.attributes.put(name, value);
return this;
}
@Override
public Builder attributes(Map<String, Object> attributes) {
Assert.notNull(attributes, "'attributes' must not be null");
this.attributes = attributes;
return this;
}
@Override
public Builder queryParam(String key, String value) {
Assert.notNull(key, "'key' must not be null");
Assert.notNull(value, "'value' must not be null");
this.queryParams.add(key, value);
return this;
}
@Override
public Builder queryParams(MultiValueMap<String, String> queryParams) {
Assert.notNull(queryParams, "'queryParams' must not be null");
this.queryParams = queryParams;
return this;
}
@Override
public Builder pathVariable(String key, String value) {
Assert.notNull(key, "'key' must not be null");
Assert.notNull(value, "'value' must not be null");
this.pathVariables.put(key, value);
return this;
}
@Override
public Builder pathVariables(Map<String, String> pathVariables) {
Assert.notNull(pathVariables, "'pathVariables' must not be null");
this.pathVariables = pathVariables;
return this;
}
@Override
public Builder session(WebSession session) {
Assert.notNull(session, "'session' must not be null");
this.session = session;
return this;
}
@Override
public MockServerRequest body(Object body) {
this.body = body;
return new MockServerRequest(this.method, this.uri, this.headers, this.body,
this.attributes, this.queryParams, this.pathVariables, this.session);
}
@Override
public MockServerRequest build() {
return new MockServerRequest(this.method, this.uri, this.headers, null,
this.attributes, this.queryParams, this.pathVariables, this.session);
}
}
private static class MockHeaders implements Headers {
private final HttpHeaders headers;
public MockHeaders(HttpHeaders headers) {
this.headers = headers;
}
private HttpHeaders delegate() {
return this.headers;
}
public void header(String key, String value) {
this.headers.add(key, value);
}
@Override
public List<MediaType> accept() {
return delegate().getAccept();
}
@Override
public List<Charset> acceptCharset() {
return delegate().getAcceptCharset();
}
@Override
public OptionalLong contentLength() {
return toOptionalLong(delegate().getContentLength());
}
@Override
public Optional<MediaType> contentType() {
return Optional.ofNullable(delegate().getContentType());
}
@Override
public InetSocketAddress host() {
return delegate().getHost();
}
@Override
public List<HttpRange> range() {
return delegate().getRange();
}
@Override
public List<String> header(String headerName) {
List<String> headerValues = delegate().get(headerName);
return headerValues != null ? headerValues : Collections.emptyList();
}
@Override
public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(delegate());
}
private OptionalLong toOptionalLong(long value) {
return value != -1 ? OptionalLong.of(value) : OptionalLong.empty();
}
private Optional<ZonedDateTime> toZonedDateTime(long date) {
if (date != -1) {
Instant instant = Instant.ofEpochMilli(date);
return Optional.of(ZonedDateTime.ofInstant(instant, ZoneId.of("GMT")));
}
else {
return Optional.empty();
}
}
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.web.reactive.function.server;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
/**
* @author Arjen Poutsma
*/
public class PathResourceLookupFunctionTests {
@Test
public void normal() throws Exception {
ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/server/");
PathResourceLookupFunction
function = new PathResourceLookupFunction("/resources/**", location);
MockServerRequest request = MockServerRequest.builder()
.uri(new URI("http://localhost/resources/response.txt"))
.build();
Mono<Resource> result = function.apply(request);
File expected = new ClassPathResource("response.txt", getClass()).getFile();
StepVerifier.create(result)
.expectNextMatches(resource -> {
try {
return expected.equals(resource.getFile());
}
catch (IOException ex) {
return false;
}
})
.expectComplete()
.verify();
}
@Test
public void subPath() throws Exception {
ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/server/");
PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location);
MockServerRequest request = MockServerRequest.builder()
.uri(new URI("http://localhost/resources/child/response.txt"))
.build();
Mono<Resource> result = function.apply(request);
File expected = new ClassPathResource("org/springframework/web/reactive/function/server/child/response.txt").getFile();
StepVerifier.create(result)
.expectNextMatches(resource -> {
try {
return expected.equals(resource.getFile());
}
catch (IOException ex) {
return false;
}
})
.expectComplete()
.verify();
}
@Test
public void notFound() throws Exception {
ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/server/");
PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location);
MockServerRequest request = MockServerRequest.builder()
.uri(new URI("http://localhost/resources/foo"))
.build();
Mono<Resource> result = function.apply(request);
StepVerifier.create(result)
.expectComplete()
.verify();
}
}

View File

@@ -0,0 +1,164 @@
/*
* 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.web.reactive.function.server;
import java.net.URI;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertEquals;
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
import static org.springframework.web.reactive.function.BodyInserters.fromPublisher;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
* @author Arjen Poutsma
*/
public class PublisherHandlerFunctionIntegrationTests
extends AbstractRouterFunctionIntegrationTests {
private RestTemplate restTemplate;
@Before
public void createRestTemplate() {
this.restTemplate = new RestTemplate();
}
@Override
protected RouterFunction<?> routerFunction() {
PersonHandler personHandler = new PersonHandler();
return route(GET("/mono"), personHandler::mono)
.and(route(POST("/mono"), personHandler::postMono))
.and(route(GET("/flux"), personHandler::flux));
}
@Test
public void mono() throws Exception {
ResponseEntity<Person> result =
restTemplate.getForEntity("http://localhost:" + port + "/mono", Person.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("John", result.getBody().getName());
}
@Test
public void flux() throws Exception {
ParameterizedTypeReference<List<Person>> reference = new ParameterizedTypeReference<List<Person>>() {};
ResponseEntity<List<Person>> result =
restTemplate.exchange("http://localhost:" + port + "/flux", HttpMethod.GET, null, reference);
assertEquals(HttpStatus.OK, result.getStatusCode());
List<Person> body = result.getBody();
assertEquals(2, body.size());
assertEquals("John", body.get(0).getName());
assertEquals("Jane", body.get(1).getName());
}
@Test
public void postMono() {
URI uri = URI.create("http://localhost:" + port + "/mono");
Person person = new Person("Jack");
RequestEntity<Person> requestEntity = RequestEntity.post(uri).body(person);
ResponseEntity<Person> result = restTemplate.exchange(requestEntity, Person.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("Jack", result.getBody().getName());
}
private static class PersonHandler {
public Mono<ServerResponse> mono(ServerRequest request) {
Person person = new Person("John");
return ServerResponse.ok().body(fromPublisher(Mono.just(person), Person.class));
}
public Mono<ServerResponse> postMono(ServerRequest request) {
Mono<Person> personMono = request.body(toMono(Person.class));
return ServerResponse.ok().body(fromPublisher(personMono, Person.class));
}
public Mono<ServerResponse> flux(ServerRequest request) {
Person person1 = new Person("John");
Person person2 = new Person("Jane");
return ServerResponse.ok().body(
fromPublisher(Flux.just(person1, person2), Person.class));
}
}
private static class Person {
private String name;
@SuppressWarnings("unused")
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Person person = (Person) o;
return !(this.name != null ? !this.name.equals(person.name) : person.name != null);
}
@Override
public int hashCode() {
return this.name != null ? this.name.hashCode() : 0;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.web.reactive.function.server;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Arjen Poutsma
*/
public class RequestPredicateTests {
@Test
public void and() throws Exception {
RequestPredicate predicate1 = request -> true;
RequestPredicate predicate2 = request -> true;
RequestPredicate predicate3 = request -> false;
MockServerRequest request = MockServerRequest.builder().build();
assertTrue(predicate1.and(predicate2).test(request));
assertTrue(predicate2.and(predicate1).test(request));
assertFalse(predicate1.and(predicate3).test(request));
}
@Test
public void negate() throws Exception {
RequestPredicate predicate = request -> false;
RequestPredicate negated = predicate.negate();
MockServerRequest mockRequest = MockServerRequest.builder().build();
assertTrue(negated.test(mockRequest));
predicate = request -> true;
negated = predicate.negate();
assertFalse(negated.test(mockRequest));
}
@Test
public void or() throws Exception {
RequestPredicate predicate1 = request -> true;
RequestPredicate predicate2 = request -> false;
RequestPredicate predicate3 = request -> false;
MockServerRequest request = MockServerRequest.builder().build();
assertTrue(predicate1.or(predicate2).test(request));
assertTrue(predicate2.or(predicate1).test(request));
assertFalse(predicate2.or(predicate3).test(request));
}
}

View File

@@ -0,0 +1,137 @@
/*
* 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.web.reactive.function.server;
import java.net.URI;
import java.util.Collections;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Arjen Poutsma
*/
public class RequestPredicatesTests {
@Test
public void all() throws Exception {
RequestPredicate predicate = RequestPredicates.all();
MockServerRequest request = MockServerRequest.builder().build();
assertTrue(predicate.test(request));
}
@Test
public void method() throws Exception {
HttpMethod httpMethod = HttpMethod.GET;
RequestPredicate predicate = RequestPredicates.method(httpMethod);
MockServerRequest request = MockServerRequest.builder().method(httpMethod).build();
assertTrue(predicate.test(request));
request = MockServerRequest.builder().method(HttpMethod.POST).build();
assertFalse(predicate.test(request));
}
@Test
public void methods() throws Exception {
URI uri = URI.create("http://localhost/path");
RequestPredicate predicate = RequestPredicates.GET("/p*");
MockServerRequest request = MockServerRequest.builder().method(HttpMethod.GET).uri(uri).build();
assertTrue(predicate.test(request));
predicate = RequestPredicates.HEAD("/p*");
request = MockServerRequest.builder().method(HttpMethod.HEAD).uri(uri).build();
assertTrue(predicate.test(request));
predicate = RequestPredicates.POST("/p*");
request = MockServerRequest.builder().method(HttpMethod.POST).uri(uri).build();
assertTrue(predicate.test(request));
predicate = RequestPredicates.PUT("/p*");
request = MockServerRequest.builder().method(HttpMethod.PUT).uri(uri).build();
assertTrue(predicate.test(request));
predicate = RequestPredicates.PATCH("/p*");
request = MockServerRequest.builder().method(HttpMethod.PATCH).uri(uri).build();
assertTrue(predicate.test(request));
predicate = RequestPredicates.DELETE("/p*");
request = MockServerRequest.builder().method(HttpMethod.DELETE).uri(uri).build();
assertTrue(predicate.test(request));
predicate = RequestPredicates.OPTIONS("/p*");
request = MockServerRequest.builder().method(HttpMethod.OPTIONS).uri(uri).build();
assertTrue(predicate.test(request));
}
@Test
public void path() throws Exception {
URI uri = URI.create("http://localhost/path");
RequestPredicate predicate = RequestPredicates.path("/p*");
MockServerRequest request = MockServerRequest.builder().uri(uri).build();
assertTrue(predicate.test(request));
request = MockServerRequest.builder().build();
assertFalse(predicate.test(request));
}
@Test
public void headers() throws Exception {
String name = "MyHeader";
String value = "MyValue";
RequestPredicate predicate =
RequestPredicates.headers(headers -> {
return headers.header(name).equals(
Collections.singletonList(value));
});
MockServerRequest request = MockServerRequest.builder().header(name, value).build();
assertTrue(predicate.test(request));
request = MockServerRequest.builder().build();
assertFalse(predicate.test(request));
}
@Test
public void contentType() throws Exception {
MediaType json = MediaType.APPLICATION_JSON;
RequestPredicate predicate = RequestPredicates.contentType(json);
MockServerRequest
request = MockServerRequest.builder().header("Content-Type", json.toString()).build();
assertTrue(predicate.test(request));
request = MockServerRequest.builder().build();
assertFalse(predicate.test(request));
}
@Test
public void accept() throws Exception {
MediaType json = MediaType.APPLICATION_JSON;
RequestPredicate predicate = RequestPredicates.accept(json);
MockServerRequest request = MockServerRequest.builder().header("Accept", json.toString()).build();
assertTrue(predicate.test(request));
request = MockServerRequest.builder().build();
assertFalse(predicate.test(request));
}
}

View File

@@ -0,0 +1,156 @@
/*
* 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.web.reactive.function.server;
import java.io.IOException;
import java.nio.file.Files;
import java.util.EnumSet;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* @author Arjen Poutsma
*/
public class ResourceHandlerFunctionTests {
private final Resource resource = new ClassPathResource("response.txt", getClass());
private final ResourceHandlerFunction handlerFunction = new ResourceHandlerFunction(this.resource);
@Test
public void get() throws IOException {
MockServerHttpRequest mockRequest = MockServerHttpRequest.get("http://localhost").build();
MockServerHttpResponse mockResponse = new MockServerHttpResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(mockRequest, mockResponse,
new MockWebSessionManager());
ServerRequest request = new DefaultServerRequest(exchange, HandlerStrategies.withDefaults());
Mono<ServerResponse> responseMono = this.handlerFunction.handle(request);
Mono<Void> result = responseMono.then(response -> {
assertEquals(HttpStatus.OK, response.statusCode());
/*
TODO: enable when ServerEntityResponse is reintroduced
StepVerifier.create(response.body())
.expectNext(this.resource)
.expectComplete()
.verify();
*/
return response.writeTo(exchange, HandlerStrategies.withDefaults());
});
StepVerifier.create(result)
.expectComplete()
.verify();
byte[] expectedBytes = Files.readAllBytes(this.resource.getFile().toPath());
StepVerifier.create(mockResponse.getBody())
.consumeNextWith(dataBuffer -> {
byte[] resultBytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(resultBytes);
assertArrayEquals(expectedBytes, resultBytes);
})
.expectComplete()
.verify();
assertEquals(MediaType.TEXT_PLAIN, mockResponse.getHeaders().getContentType());
assertEquals(this.resource.contentLength(), mockResponse.getHeaders().getContentLength());
}
@Test
public void head() throws IOException {
MockServerHttpRequest mockRequest = MockServerHttpRequest.head("http://localhost").build();
MockServerHttpResponse mockResponse = new MockServerHttpResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(mockRequest, mockResponse,
new MockWebSessionManager());
ServerRequest request = new DefaultServerRequest(exchange, HandlerStrategies.withDefaults());
Mono<ServerResponse> response = this.handlerFunction.handle(request);
Mono<Void> result = response.then(res -> {
assertEquals(HttpStatus.OK, res.statusCode());
return res.writeTo(exchange, HandlerStrategies.withDefaults());
});
StepVerifier.create(result)
.expectComplete()
.verify();
StepVerifier.create(result).expectComplete().verify();
StepVerifier.create(mockResponse.getBody())
.expectComplete()
.verify();
assertEquals(MediaType.TEXT_PLAIN, mockResponse.getHeaders().getContentType());
assertEquals(this.resource.contentLength(), mockResponse.getHeaders().getContentLength());
}
@Test
public void options() {
MockServerHttpRequest mockRequest = MockServerHttpRequest.options("http://localhost").build();
MockServerHttpResponse mockResponse = new MockServerHttpResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(mockRequest, mockResponse,
new MockWebSessionManager());
ServerRequest request = new DefaultServerRequest(exchange, HandlerStrategies.withDefaults());
Mono<ServerResponse> responseMono = this.handlerFunction.handle(request);
Mono<Void> result = responseMono.then(response -> {
assertEquals(HttpStatus.OK, response.statusCode());
assertEquals(EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS),
response.headers().getAllow());
/*
TODO: enable when ServerEntityResponse is reintroduced
StepVerifier.create(response.body())
.expectComplete()
.verify();
*/
return response.writeTo(exchange, HandlerStrategies.withDefaults());
});
StepVerifier.create(result)
.expectComplete()
.verify();
assertEquals(HttpStatus.OK, mockResponse.getStatusCode());
assertEquals(EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS),
mockResponse.getHeaders().getAllow());
assertNull(mockResponse.getBody());
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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.web.reactive.function.server;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.junit.Assert.assertNotNull;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
/**
* @author Arjen Poutsma
*/
@SuppressWarnings("unchecked")
public class RouterFunctionTests {
@Test
public void andSame() throws Exception {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<ServerResponse> routerFunction1 = request -> Mono.empty();
RouterFunction<ServerResponse> routerFunction2 = request -> Mono.just(handlerFunction);
RouterFunction<ServerResponse> result = routerFunction1.andSame(routerFunction2);
assertNotNull(result);
MockServerRequest request = MockServerRequest.builder().build();
Mono<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectNext(handlerFunction)
.expectComplete()
.verify();
}
@Test
public void and() throws Exception {
HandlerFunction<ServerResponse> handlerFunction =
request -> ServerResponse.ok().body(fromObject("42"));
RouterFunction<?> routerFunction1 = request -> Mono.empty();
RouterFunction<ServerResponse> routerFunction2 =
request -> Mono.just(handlerFunction);
RouterFunction<?> result = routerFunction1.and(routerFunction2);
assertNotNull(result);
MockServerRequest request = MockServerRequest.builder().build();
Mono<? extends HandlerFunction<?>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectNextMatches(o -> o.equals(handlerFunction))
.expectComplete()
.verify();
}
@Test
public void andRoute() throws Exception {
RouterFunction<?> routerFunction1 = request -> Mono.empty();
RequestPredicate requestPredicate = request -> true;
RouterFunction<?> result = routerFunction1.andRoute(requestPredicate, this::handlerMethod);
assertNotNull(result);
MockServerRequest request = MockServerRequest.builder().build();
Mono<? extends HandlerFunction<?>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectNextCount(1)
.expectComplete()
.verify();
}
private Mono<ServerResponse> handlerMethod(ServerRequest request) {
return ServerResponse.ok().body(fromObject("42"));
}
/*
TODO: enable when ServerEntityResponse is reintroduced
@Test
public void filter() throws Exception {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().body(fromObject("42"));
RouterFunction<ServerResponse> routerFunction = request -> Mono.just(handlerFunction);
HandlerFilterFunction<String, Integer> filterFunction =
(request, next) -> next.handle(request).then(
response -> {
Flux<Integer> body = Flux.from(response.body())
.map(Integer::parseInt);
return ServerResponse.ok().body(body, Integer.class);
});
RouterFunction<Integer> result = routerFunction.filter(filterFunction);
assertNotNull(result);
MockServerRequest request = MockServerRequest.builder().build();
Mono<? extends ServerResponse<Integer>> responseMono =
result.route(request).then(hf -> hf.handle(request));
StepVerifier.create(responseMono)
.consumeNextWith(
serverResponse -> {
StepVerifier.create(serverResponse.body())
.expectNext(42)
.expectComplete()
.verify();
})
.expectComplete()
.verify();
}
*/
}

View File

@@ -0,0 +1,152 @@
/*
* 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.web.reactive.function.server;
import java.util.stream.Stream;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Arjen Poutsma
*/
@SuppressWarnings("unchecked")
public class RouterFunctionsTests {
@Test
public void routeMatch() throws Exception {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
MockServerRequest request = MockServerRequest.builder().build();
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(true);
RouterFunction<ServerResponse>
result = RouterFunctions.route(requestPredicate, handlerFunction);
assertNotNull(result);
Mono<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectNext(handlerFunction)
.expectComplete()
.verify();
}
@Test
public void routeNoMatch() throws Exception {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
MockServerRequest request = MockServerRequest.builder().build();
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(false);
RouterFunction<ServerResponse> result = RouterFunctions.route(requestPredicate, handlerFunction);
assertNotNull(result);
Mono<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectComplete()
.verify();
}
@Test
public void subrouteMatch() throws Exception {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<ServerResponse> routerFunction = request -> Mono.just(handlerFunction);
MockServerRequest request = MockServerRequest.builder().build();
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(true);
RouterFunction<ServerResponse> result = RouterFunctions.subroute(requestPredicate, routerFunction);
assertNotNull(result);
Mono<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectNext(handlerFunction)
.expectComplete()
.verify();
}
@Test
public void subrouteNoMatch() throws Exception {
HandlerFunction<ServerResponse> handlerFunction = request -> ServerResponse.ok().build();
RouterFunction<ServerResponse> routerFunction = request -> Mono.just(handlerFunction);
MockServerRequest request = MockServerRequest.builder().build();
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(false);
RouterFunction<ServerResponse> result = RouterFunctions.subroute(requestPredicate, routerFunction);
assertNotNull(result);
Mono<HandlerFunction<ServerResponse>> resultHandlerFunction = result.route(request);
StepVerifier.create(resultHandlerFunction)
.expectComplete()
.verify();
}
@Test
public void toHttpHandler() throws Exception {
HandlerStrategies strategies = mock(HandlerStrategies.class);
when(strategies.messageReaders()).thenReturn(
Stream::<HttpMessageReader<?>>empty);
when(strategies.messageWriters()).thenReturn(
Stream::<HttpMessageWriter<?>>empty);
when(strategies.viewResolvers()).thenReturn(
Stream::<ViewResolver>empty);
ServerRequest request = mock(ServerRequest.class);
ServerResponse response = mock(ServerResponse.class);
when(response.writeTo(any(ServerWebExchange.class), eq(strategies))).thenReturn(Mono.empty());
HandlerFunction<ServerResponse> handlerFunction = mock(HandlerFunction.class);
when(handlerFunction.handle(any(ServerRequest.class))).thenReturn(Mono.just(response));
RouterFunction<ServerResponse> routerFunction = mock(RouterFunction.class);
when(routerFunction.route(any(ServerRequest.class))).thenReturn(Mono.just(handlerFunction));
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(false);
HttpHandler result = RouterFunctions.toHttpHandler(routerFunction, strategies);
assertNotNull(result);
MockServerHttpRequest httpRequest = MockServerHttpRequest.get("http://localhost").build();
MockServerHttpResponse serverHttpResponse = new MockServerHttpResponse();
result.handle(httpRequest, serverHttpResponse);
}
}

View File

@@ -0,0 +1,186 @@
/*
* 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.function.server;
import java.time.Duration;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.reactive.function.client.WebClient;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
import static org.springframework.web.reactive.function.BodyInserters.fromServerSentEvents;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
* @author Arjen Poutsma
*/
public class SseHandlerFunctionIntegrationTests extends AbstractRouterFunctionIntegrationTests {
private WebClient webClient;
@Before
public void setup() throws Exception {
super.setup();
this.webClient = WebClient.create("http://localhost:" + this.port);
}
@Override
protected RouterFunction<?> routerFunction() {
SseHandler sseHandler = new SseHandler();
return route(RequestPredicates.GET("/string"), sseHandler::string)
.and(route(RequestPredicates.GET("/person"), sseHandler::person))
.and(route(RequestPredicates.GET("/event"), sseHandler::sse));
}
@Test
public void sseAsString() throws Exception {
Flux<String> result = this.webClient.get()
.uri("/string")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.body(toFlux(String.class)));
StepVerifier.create(result)
.expectNext("foo 0")
.expectNext("foo 1")
.expectComplete()
.verify(Duration.ofSeconds(5L));
}
@Test
public void sseAsPerson() throws Exception {
Flux<Person> result = this.webClient.get()
.uri("/person")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.body(toFlux(Person.class)));
StepVerifier.create(result)
.expectNext(new Person("foo 0"))
.expectNext(new Person("foo 1"))
.expectComplete()
.verify(Duration.ofSeconds(5L));
}
@Test
public void sseAsEvent() throws Exception {
Flux<ServerSentEvent<String>> result = this.webClient.get()
.uri("/event")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.body(toFlux(
forClassWithGenerics(ServerSentEvent.class, String.class))));
StepVerifier.create(result)
.consumeNextWith( event -> {
assertEquals("0", event.id().get());
assertEquals("foo", event.data().get());
assertEquals("bar", event.comment().get());
assertFalse(event.event().isPresent());
assertFalse(event.retry().isPresent());
})
.consumeNextWith( event -> {
assertEquals("1", event.id().get());
assertEquals("foo", event.data().get());
assertEquals("bar", event.comment().get());
assertFalse(event.event().isPresent());
assertFalse(event.retry().isPresent());
})
.expectComplete()
.verify(Duration.ofSeconds(5L));
}
private static class SseHandler {
public Mono<ServerResponse> string(ServerRequest request) {
Flux<String> flux = Flux.interval(Duration.ofMillis(100)).map(l -> "foo " + l).take(2);
return ServerResponse.ok().body(fromServerSentEvents(flux, String.class));
}
public Mono<ServerResponse> person(ServerRequest request) {
Flux<Person> flux = Flux.interval(Duration.ofMillis(100))
.map(l -> new Person("foo " + l)).take(2);
return ServerResponse.ok().body(fromServerSentEvents(flux, Person.class));
}
public Mono<ServerResponse> sse(ServerRequest request) {
Flux<ServerSentEvent<String>> flux = Flux.interval(Duration.ofMillis(100))
.map(l -> ServerSentEvent.<String>builder().data("foo")
.id(Long.toString(l))
.comment("bar")
.build()).take(2);
return ServerResponse.ok().body(fromServerSentEvents(flux));
}
}
private static class Person {
private String name;
@SuppressWarnings("unused")
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Person person = (Person) o;
return !(this.name != null ? !this.name.equals(person.name) : person.name != null);
}
@Override
public int hashCode() {
return this.name != null ? this.name.hashCode() : 0;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
}

View File

@@ -0,0 +1,132 @@
/*
* 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.web.reactive.function.server.support;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.server.ServerRequest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Arjen Poutsma
*/
public class ServerRequestWrapperTests {
private ServerRequest mockRequest;
private ServerRequestWrapper wrapper;
@Before
public void createWrapper() {
mockRequest = mock(ServerRequest.class);
wrapper = new ServerRequestWrapper(mockRequest);
}
@Test
public void request() throws Exception {
assertSame(mockRequest, wrapper.request());
}
@Test
public void method() throws Exception {
HttpMethod method = HttpMethod.POST;
when(mockRequest.method()).thenReturn(method);
assertSame(method, wrapper.method());
}
@Test
public void uri() throws Exception {
URI uri = URI.create("https://example.com");
when(mockRequest.uri()).thenReturn(uri);
assertSame(uri, wrapper.uri());
}
@Test
public void path() throws Exception {
String path = "/foo/bar";
when(mockRequest.path()).thenReturn(path);
assertSame(path, wrapper.path());
}
@Test
public void headers() throws Exception {
ServerRequest.Headers headers = mock(ServerRequest.Headers.class);
when(mockRequest.headers()).thenReturn(headers);
assertSame(headers, wrapper.headers());
}
@Test
public void attribute() throws Exception {
String name = "foo";
String value = "bar";
when(mockRequest.attribute(name)).thenReturn(Optional.of(value));
assertEquals(Optional.of(value), wrapper.attribute(name));
}
@Test
public void queryParam() throws Exception {
String name = "foo";
String value = "bar";
when(mockRequest.queryParam(name)).thenReturn(Optional.of(value));
assertEquals(Optional.of(value), wrapper.queryParam(name));
}
@Test
public void queryParams() throws Exception {
String name = "foo";
List<String> value = Collections.singletonList("bar");
when(mockRequest.queryParams(name)).thenReturn(value);
assertSame(value, wrapper.queryParams(name));
}
@Test
public void pathVariable() throws Exception {
String name = "foo";
String value = "bar";
when(mockRequest.pathVariable(name)).thenReturn(value);
assertEquals(value, wrapper.pathVariable(name));
}
@Test
public void pathVariables() throws Exception {
Map<String, String> pathVariables = Collections.singletonMap("foo", "bar");
when(mockRequest.pathVariables()).thenReturn(pathVariables);
assertSame(pathVariables, wrapper.pathVariables());
}
}

View File

@@ -0,0 +1,159 @@
/*
* 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.web.reactive.handler;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
/**
* Unit tests for CORS support at {@link AbstractUrlHandlerMapping} level.
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
*/
public class CorsUrlHandlerMappingTests {
private AbstractUrlHandlerMapping handlerMapping;
private Object welcomeController = new Object();
private CorsAwareHandler corsController = new CorsAwareHandler();
@Before
public void setup() {
this.handlerMapping = new AbstractUrlHandlerMapping() {};
this.handlerMapping.setUseTrailingSlashMatch(true);
this.handlerMapping.registerHandler("/welcome.html", this.welcomeController);
this.handlerMapping.registerHandler("/cors.html", this.corsController);
}
@Test
public void actualRequestWithoutCorsConfigurationProvider() throws Exception {
String origin = "http://domain2.com";
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/welcome.html", origin, "GET");
Object actual = this.handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertSame(this.welcomeController, actual);
}
@Test
public void preflightRequestWithoutCorsConfigurationProvider() throws Exception {
String origin = "http://domain2.com";
ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/welcome.html", origin, "GET");
Object actual = this.handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertNotSame(this.welcomeController, actual);
assertNull(exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
@Test
public void actualRequestWithCorsAwareHandler() throws Exception {
String origin = "http://domain2.com";
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/cors.html", origin, "GET");
Object actual = this.handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertSame(this.corsController, actual);
assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
@Test
public void preFlightWithCorsAwareHandler() throws Exception {
String origin = "http://domain2.com";
ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/cors.html", origin, "GET");
Object actual = this.handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertNotSame(this.corsController, actual);
assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
@Test
public void actualRequestWithGlobalCorsConfig() throws Exception {
CorsConfiguration mappedConfig = new CorsConfiguration();
mappedConfig.addAllowedOrigin("*");
this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/welcome.html", mappedConfig));
String origin = "http://domain2.com";
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/welcome.html", origin, "GET");
Object actual = this.handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertSame(this.welcomeController, actual);
assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
@Test
public void preFlightRequestWithGlobalCorsConfig() throws Exception {
CorsConfiguration mappedConfig = new CorsConfiguration();
mappedConfig.addAllowedOrigin("*");
this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/welcome.html", mappedConfig));
String origin = "http://domain2.com";
ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/welcome.html", origin, "GET");
Object actual = this.handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertNotSame(this.welcomeController, actual);
assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
private ServerWebExchange createExchange(HttpMethod method, String path, String origin,
String accessControlRequestMethod) {
ServerHttpRequest request = MockServerHttpRequest
.method(method, "http://localhost" + path)
.header("Origin", origin)
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, accessControlRequestMethod)
.build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
private class CorsAwareHandler implements CorsConfigurationSource {
@Override
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
return config;
}
}
}

View File

@@ -0,0 +1,150 @@
/*
* 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.web.reactive.handler;
import java.net.URI;
import java.net.URISyntaxException;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.springframework.web.reactive.HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE;
/**
* Unit tests for {@link SimpleUrlHandlerMapping}.
*
* @author Rossen Stoyanchev
*/
public class SimpleUrlHandlerMappingTests {
@Test
public void handlerMappingJavaConfig() throws Exception {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
HandlerMapping handlerMapping = (HandlerMapping) wac.getBean("handlerMapping");
Object mainController = wac.getBean("mainController");
Object otherController = wac.getBean("otherController");
testUrl("/welcome.html", mainController, handlerMapping, "/welcome.html");
testUrl("/welcome.x", otherController, handlerMapping, "welcome.x");
testUrl("/welcome/", otherController, handlerMapping, "welcome");
testUrl("/show.html", mainController, handlerMapping, "/show.html");
testUrl("/bookseats.html", mainController, handlerMapping, "/bookseats.html");
}
@Test
public void handlerMappingXmlConfig() throws Exception {
ClassPathXmlApplicationContext wac = new ClassPathXmlApplicationContext("map.xml", getClass());
wac.refresh();
HandlerMapping handlerMapping = wac.getBean("mapping", HandlerMapping.class);
Object mainController = wac.getBean("mainController");
testUrl("/pathmatchingTest.html", mainController, handlerMapping, "pathmatchingTest.html");
testUrl("welcome.html", null, handlerMapping, null);
testUrl("/pathmatchingAA.html", mainController, handlerMapping, "pathmatchingAA.html");
testUrl("/pathmatchingA.html", null, handlerMapping, null);
testUrl("/administrator/pathmatching.html", mainController, handlerMapping, "pathmatching.html");
testUrl("/administrator/test/pathmatching.html", mainController, handlerMapping, "test/pathmatching.html");
testUrl("/administratort/pathmatching.html", null, handlerMapping, null);
testUrl("/administrator/another/bla.xml", mainController, handlerMapping, "/administrator/another/bla.xml");
testUrl("/administrator/another/bla.gif", null, handlerMapping, null);
testUrl("/administrator/test/testlastbit", mainController, handlerMapping, "test/testlastbit");
testUrl("/administrator/test/testla", null, handlerMapping, null);
testUrl("/administrator/testing/longer/bla", mainController, handlerMapping, "bla");
testUrl("/administrator/testing/longer2/notmatching/notmatching", null, handlerMapping, null);
testUrl("/shortpattern/testing/toolong", null, handlerMapping, null);
testUrl("/XXpathXXmatching.html", mainController, handlerMapping, "XXpathXXmatching.html");
testUrl("/pathXXmatching.html", mainController, handlerMapping, "pathXXmatching.html");
testUrl("/XpathXXmatching.html", null, handlerMapping, null);
testUrl("/XXpathmatching.html", null, handlerMapping, null);
testUrl("/show12.html", mainController, handlerMapping, "show12.html");
testUrl("/show123.html", mainController, handlerMapping, "/show123.html");
testUrl("/show1.html", mainController, handlerMapping, "show1.html");
testUrl("/reallyGood-test-is-this.jpeg", mainController, handlerMapping, "reallyGood-test-is-this.jpeg");
testUrl("/reallyGood-tst-is-this.jpeg", null, handlerMapping, null);
testUrl("/testing/test.jpeg", mainController, handlerMapping, "testing/test.jpeg");
testUrl("/testing/test.jpg", null, handlerMapping, null);
testUrl("/anotherTest", mainController, handlerMapping, "anotherTest");
testUrl("/stillAnotherTest", null, handlerMapping, null);
testUrl("outofpattern*ye", null, handlerMapping, null);
testUrl("/test%26t%20est/path%26m%20atching.html", null, handlerMapping, null);
}
private void testUrl(String url, Object bean, HandlerMapping handlerMapping, String pathWithinMapping) {
ServerWebExchange exchange = createExchange(url);
Object actual = handlerMapping.getHandler(exchange).block();
if (bean != null) {
assertNotNull(actual);
assertSame(bean, actual);
//noinspection OptionalGetWithoutIsPresent
assertEquals(pathWithinMapping, exchange.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).get());
}
else {
assertNull(actual);
}
}
private ServerWebExchange createExchange(String path) {
ServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, URI.create(path)).build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
@Configuration
static class WebConfig {
@Bean @SuppressWarnings("unused")
public SimpleUrlHandlerMapping handlerMapping() {
SimpleUrlHandlerMapping hm = new SimpleUrlHandlerMapping();
hm.setUseTrailingSlashMatch(true);
hm.registerHandler("/welcome*", otherController());
hm.registerHandler("/welcome.html", mainController());
hm.registerHandler("/show.html", mainController());
hm.registerHandler("/bookseats.html", mainController());
return hm;
}
@Bean
public Object mainController() {
return new Object();
}
@Bean
public Object otherController() {
return new Object();
}
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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.web.reactive.resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpMethod;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.mock;
/**
* Unit tests for {@link AppCacheManifestTransformer}.
* @author Rossen Stoyanchev
* @author Brian Clozel
*/
public class AppCacheManifestTransformerTests {
private AppCacheManifestTransformer transformer;
private ResourceTransformerChain chain;
@Before
public void setup() {
ClassPathResource allowedLocation = new ClassPathResource("test/", getClass());
ResourceWebHandler resourceHandler = new ResourceWebHandler();
ResourceUrlProvider resourceUrlProvider = new ResourceUrlProvider();
resourceUrlProvider.setHandlerMap(Collections.singletonMap("/static/**", resourceHandler));
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
PathResourceResolver pathResolver = new PathResourceResolver();
pathResolver.setAllowedLocations(allowedLocation);
List<ResourceResolver> resolvers = Arrays.asList(versionResolver, pathResolver);
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
CssLinkResourceTransformer cssLinkResourceTransformer = new CssLinkResourceTransformer();
cssLinkResourceTransformer.setResourceUrlProvider(resourceUrlProvider);
List<ResourceTransformer> transformers = Collections.singletonList(cssLinkResourceTransformer);
this.chain = new DefaultResourceTransformerChain(resolverChain, transformers);
this.transformer = new AppCacheManifestTransformer();
this.transformer.setResourceUrlProvider(resourceUrlProvider);
resourceHandler.setResourceResolvers(resolvers);
resourceHandler.setResourceTransformers(transformers);
resourceHandler.setLocations(Collections.singletonList(allowedLocation));
}
@Test
public void noTransformIfExtensionNoMatch() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/static/foobar.file");
this.chain = mock(ResourceTransformerChain.class);
Resource resource = mock(Resource.class);
given(resource.getFilename()).willReturn("foobar.file");
given(this.chain.transform(exchange, resource)).willReturn(Mono.just(resource));
Resource result = this.transformer.transform(exchange, resource, this.chain).blockMillis(5000);
assertEquals(resource, result);
}
@Test
public void syntaxErrorInManifest() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/static/error.appcache");
this.chain = mock(ResourceTransformerChain.class);
Resource resource = new ClassPathResource("test/error.appcache", getClass());
given(this.chain.transform(exchange, resource)).willReturn(Mono.just(resource));
Resource result = this.transformer.transform(exchange, resource, this.chain).blockMillis(5000);
assertEquals(resource, result);
}
@Test
public void transformManifest() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/static/test.appcache");
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
PathResourceResolver pathResolver = new PathResourceResolver();
pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass()));
List<ResourceResolver> resolvers = Arrays.asList(versionResolver, pathResolver);
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
List<ResourceTransformer> transformers = new ArrayList<>();
transformers.add(new CssLinkResourceTransformer());
this.chain = new DefaultResourceTransformerChain(resolverChain, transformers);
Resource resource = new ClassPathResource("test/test.appcache", getClass());
Resource result = this.transformer.transform(exchange, resource, this.chain).blockMillis(5000);
byte[] bytes = FileCopyUtils.copyToByteArray(result.getInputStream());
String content = new String(bytes, "UTF-8");
assertThat("should rewrite resource links", content,
Matchers.containsString("/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css"));
assertThat("should rewrite resource links", content,
Matchers.containsString("/static/bar-11e16cf79faee7ac698c805cf28248d2.css"));
assertThat("should rewrite resource links", content,
Matchers.containsString("/static/js/bar-bd508c62235b832d960298ca6c0b7645.js"));
assertThat("should not rewrite external resources", content,
Matchers.containsString("//example.org/style.css"));
assertThat("should not rewrite external resources", content,
Matchers.containsString("http://example.org/image.png"));
assertThat("should generate fingerprint", content,
Matchers.containsString("# Hash: 4bf0338bcbeb0a5b3a4ec9ed8864107d"));
}
private ServerWebExchange createExchange(HttpMethod method, String url) {
MockServerHttpRequest request = MockServerHttpRequest.method(method, url).build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,161 @@
/*
* 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.web.reactive.resource;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
/**
* Unit tests for {@link CachingResourceResolver}.
* @author Rossen Stoyanchev
*/
public class CachingResourceResolverTests {
private Cache cache;
private ResourceResolverChain chain;
private List<Resource> locations;
private MockServerHttpRequest request;
@Before
public void setup() {
this.cache = new ConcurrentMapCache("resourceCache");
List<ResourceResolver> resolvers = new ArrayList<>();
resolvers.add(new CachingResourceResolver(this.cache));
resolvers.add(new PathResourceResolver());
this.chain = new DefaultResourceResolverChain(resolvers);
this.locations = new ArrayList<>();
this.locations.add(new ClassPathResource("test/", getClass()));
this.request = MockServerHttpRequest.get("").build();
}
@Test
public void resolveResourceInternal() {
String file = "bar.css";
Resource expected = new ClassPathResource("test/" + file, getClass());
Resource actual = this.chain.resolveResource(createExchange(), file, this.locations).blockMillis(5000);
assertEquals(expected, actual);
}
@Test
public void resolveResourceInternalFromCache() {
Resource expected = Mockito.mock(Resource.class);
this.cache.put(CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + "bar.css", expected);
String file = "bar.css";
Resource actual = this.chain.resolveResource(createExchange(), file, this.locations).blockMillis(5000);
assertSame(expected, actual);
}
@Test
public void resolveResourceInternalNoMatch() {
assertNull(this.chain.resolveResource(createExchange(), "invalid.css", this.locations).blockMillis(5000));
}
@Test
public void resolverUrlPath() {
String expected = "/foo.css";
String actual = this.chain.resolveUrlPath(expected, this.locations).blockMillis(5000);
assertEquals(expected, actual);
}
@Test
public void resolverUrlPathFromCache() {
String expected = "cached-imaginary.css";
this.cache.put(CachingResourceResolver.RESOLVED_URL_PATH_CACHE_KEY_PREFIX + "imaginary.css", expected);
String actual = this.chain.resolveUrlPath("imaginary.css", this.locations).blockMillis(5000);
assertEquals(expected, actual);
}
@Test
public void resolverUrlPathNoMatch() {
assertNull(this.chain.resolveUrlPath("invalid.css", this.locations).blockMillis(5000));
}
@Test
public void resolveResourceAcceptEncodingInCacheKey() {
String file = "bar.css";
this.request = MockServerHttpRequest.get(file).header("Accept-Encoding", "gzip").build();
Resource expected = this.chain.resolveResource(createExchange(), file, this.locations).blockMillis(5000);
String cacheKey = CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + file + "+encoding=gzip";
assertEquals(expected, this.cache.get(cacheKey).get());
}
@Test
public void resolveResourceNoAcceptEncodingInCacheKey() {
String file = "bar.css";
this.request = MockServerHttpRequest.get(file).build();
Resource expected = this.chain.resolveResource(createExchange(), file, this.locations).blockMillis(5000);
String cacheKey = CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + file;
assertEquals(expected, this.cache.get(cacheKey).get());
}
@Test
public void resolveResourceMatchingEncoding() {
Resource resource = Mockito.mock(Resource.class);
Resource gzResource = Mockito.mock(Resource.class);
this.cache.put(CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + "bar.css", resource);
this.cache.put(CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + "bar.css+encoding=gzip", gzResource);
String file = "bar.css";
this.request = MockServerHttpRequest.get(file).build();
assertSame(resource, this.chain.resolveResource(createExchange(), file, this.locations).blockMillis(5000));
this.request = MockServerHttpRequest.get(file).header("Accept-Encoding", "gzip").build();
assertSame(gzResource, this.chain.resolveResource(createExchange(), file, this.locations).blockMillis(5000));
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.web.reactive.resource;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.DigestUtils;
import org.springframework.util.FileCopyUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Unit tests for {@link ContentVersionStrategy}.
* @author Rossen Stoyanchev
* @author Brian Clozel
*/
public class ContentBasedVersionStrategyTests {
private ContentVersionStrategy strategy = new ContentVersionStrategy();
@Before
public void setup() {
VersionResourceResolver versionResourceResolver = new VersionResourceResolver();
versionResourceResolver.setStrategyMap(Collections.singletonMap("/**", this.strategy));
}
@Test
public void extractVersion() throws Exception {
String hash = "7fbe76cdac6093784895bb4989203e5a";
String path = "font-awesome/css/font-awesome.min-" + hash + ".css";
assertEquals(hash, this.strategy.extractVersion(path));
assertNull(this.strategy.extractVersion("foo/bar.css"));
}
@Test
public void removeVersion() throws Exception {
String file = "font-awesome/css/font-awesome.min%s%s.css";
String hash = "7fbe76cdac6093784895bb4989203e5a";
assertEquals(String.format(file, "", ""), this.strategy.removeVersion(String.format(file, "-", hash), hash));
assertNull(this.strategy.extractVersion("foo/bar.css"));
}
@Test
public void getResourceVersion() throws Exception {
Resource expected = new ClassPathResource("test/bar.css", getClass());
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(expected.getInputStream()));
assertEquals(hash, this.strategy.getResourceVersion(expected));
}
@Test
public void addVersionToUrl() throws Exception {
String requestPath = "test/bar.css";
String version = "123";
assertEquals("test/bar-123.css", this.strategy.addVersion(requestPath, version));
}
}

View File

@@ -0,0 +1,173 @@
/*
* 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.web.reactive.resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import reactor.test.StepVerifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
/**
* Unit tests for {@link CssLinkResourceTransformer}.
* @author Rossen Stoyanchev
*/
public class CssLinkResourceTransformerTests {
private ResourceTransformerChain transformerChain;
@Before
public void setUp() {
ClassPathResource allowedLocation = new ClassPathResource("test/", getClass());
ResourceWebHandler resourceHandler = new ResourceWebHandler();
ResourceUrlProvider resourceUrlProvider = new ResourceUrlProvider();
resourceUrlProvider.setHandlerMap(Collections.singletonMap("/static/**", resourceHandler));
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
PathResourceResolver pathResolver = new PathResourceResolver();
pathResolver.setAllowedLocations(allowedLocation);
List<ResourceResolver> resolvers = Arrays.asList(versionResolver, pathResolver);
CssLinkResourceTransformer cssLinkResourceTransformer = new CssLinkResourceTransformer();
cssLinkResourceTransformer.setResourceUrlProvider(resourceUrlProvider);
List<ResourceTransformer> transformers = Collections.singletonList(cssLinkResourceTransformer);
resourceHandler.setResourceResolvers(resolvers);
resourceHandler.setResourceTransformers(transformers);
resourceHandler.setLocations(Collections.singletonList(allowedLocation));
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
this.transformerChain = new DefaultResourceTransformerChain(resolverChain, transformers);
}
@Test
public void transform() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/static/main.css");
Resource css = new ClassPathResource("test/main.css", getClass());
String expected = "\n" +
"@import url(\"/static/bar-11e16cf79faee7ac698c805cf28248d2.css\");\n" +
"@import url('/static/bar-11e16cf79faee7ac698c805cf28248d2.css');\n" +
"@import url(/static/bar-11e16cf79faee7ac698c805cf28248d2.css);\n\n" +
"@import \"/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css\";\n" +
"@import '/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" +
"body { background: url(\"/static/images/image-f448cd1d5dba82b774f3202c878230b3.png\") }\n";
StepVerifier.create(this.transformerChain.transform(exchange, css).cast(TransformedResource.class))
.consumeNextWith(resource -> {
String result = new String(resource.getByteArray(), StandardCharsets.UTF_8);
result = StringUtils.deleteAny(result, "\r");
assertEquals(expected, result);
})
.expectComplete().verify();
}
@Test
public void transformNoLinks() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/static/foo.css");
Resource expected = new ClassPathResource("test/foo.css", getClass());
StepVerifier.create(this.transformerChain.transform(exchange, expected))
.consumeNextWith(resource -> assertSame(expected, resource))
.expectComplete().verify();
}
@Test
public void transformExtLinksNotAllowed() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/static/external.css");
ResourceResolverChain resolverChain = Mockito.mock(DefaultResourceResolverChain.class);
ResourceTransformerChain transformerChain = new DefaultResourceTransformerChain(resolverChain,
Collections.singletonList(new CssLinkResourceTransformer()));
Resource externalCss = new ClassPathResource("test/external.css", getClass());
StepVerifier.create(transformerChain.transform(exchange, externalCss).cast(TransformedResource.class))
.consumeNextWith(resource -> {
String expected = "@import url(\"http://example.org/fonts/css\");\n" +
"body { background: url(\"file:///home/spring/image.png\") }\n" +
"figure { background: url(\"//example.org/style.css\")}";
String result = new String(resource.getByteArray(), StandardCharsets.UTF_8);
result = StringUtils.deleteAny(result, "\r");
assertEquals(expected, result);
}).expectComplete().verify();
Mockito.verify(resolverChain, Mockito.never())
.resolveUrlPath("http://example.org/fonts/css", Collections.singletonList(externalCss));
Mockito.verify(resolverChain, Mockito.never())
.resolveUrlPath("file:///home/spring/image.png", Collections.singletonList(externalCss));
Mockito.verify(resolverChain, Mockito.never())
.resolveUrlPath("//example.org/style.css", Collections.singletonList(externalCss));
}
@Test
public void transformWithNonCssResource() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/static/images/image.png");
Resource expected = new ClassPathResource("test/images/image.png", getClass());
StepVerifier.create(this.transformerChain.transform(exchange, expected))
.expectNext(expected)
.expectComplete().verify();
}
@Test
public void transformWithGzippedResource() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/static/main.css");
Resource original = new ClassPathResource("test/main.css", getClass());
createTempCopy("main.css", "main.css.gz");
GzipResourceResolver.GzippedResource expected = new GzipResourceResolver.GzippedResource(original);
StepVerifier.create(this.transformerChain.transform(exchange, expected))
.expectNext(expected)
.expectComplete().verify();
}
private void createTempCopy(String filePath, String copyFilePath) throws IOException {
Resource location = new ClassPathResource("test/", CssLinkResourceTransformerTests.class);
Path original = Paths.get(location.getFile().getAbsolutePath(), filePath);
Path copy = Paths.get(location.getFile().getAbsolutePath(), copyFilePath);
Files.deleteIfExists(copy);
Files.copy(original, copy);
copy.toFile().deleteOnExit();
}
private ServerWebExchange createExchange(HttpMethod method, String url) {
MockServerHttpRequest request = MockServerHttpRequest.method(method, url).build();
ServerHttpResponse response = new MockServerHttpResponse();
return new DefaultServerWebExchange(request, response);
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.web.reactive.resource;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Unit tests for {@link FixedVersionStrategy}.
* @author Rossen Stoyanchev
* @author Brian Clozel
*/
public class FixedVersionStrategyTests {
private final String version = "1df341f";
private final String path = "js/foo.js";
private FixedVersionStrategy strategy;
@Before
public void setup() {
this.strategy = new FixedVersionStrategy(this.version);
}
@Test(expected = IllegalArgumentException.class)
public void emptyPrefixVersion() throws Exception {
new FixedVersionStrategy(" ");
}
@Test
public void extractVersion() throws Exception {
assertEquals(this.version, this.strategy.extractVersion(this.version + "/" + this.path));
assertNull(this.strategy.extractVersion(this.path));
}
@Test
public void removeVersion() throws Exception {
assertEquals("/" + this.path, this.strategy.removeVersion(this.version + "/" + this.path, this.version));
}
@Test
public void addVersion() throws Exception {
assertEquals(this.version + "/" + this.path, this.strategy.addVersion("/" + this.path, this.version));
}
@Test // SPR-13727
public void addVersionRelativePath() throws Exception {
String relativePath = "../" + this.path;
assertEquals(relativePath, this.strategy.addVersion(relativePath, this.version));
}
}

View File

@@ -0,0 +1,180 @@
/*
* 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.web.reactive.resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link GzipResourceResolver}.
* @author Rossen Stoyanchev
*/
public class GzipResourceResolverTests {
private ResourceResolverChain resolver;
private List<Resource> locations;
private Cache cache;
private MockServerHttpRequest request;
@BeforeClass
public static void createGzippedResources() throws IOException {
createGzFile("/js/foo.js");
createGzFile("foo-e36d2e05253c6c7085a91522ce43a0b4.css");
}
private static void createGzFile(String filePath) throws IOException {
Resource location = new ClassPathResource("test/", GzipResourceResolverTests.class);
Resource fileResource = new FileSystemResource(location.createRelative(filePath).getFile());
Path gzFilePath = Paths.get(fileResource.getFile().getAbsolutePath() + ".gz");
Files.deleteIfExists(gzFilePath);
File gzFile = Files.createFile(gzFilePath).toFile();
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(gzFile));
FileCopyUtils.copy(fileResource.getInputStream(), out);
gzFile.deleteOnExit();
}
@Before
public void setUp() {
this.cache = new ConcurrentMapCache("resourceCache");
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
versionStrategyMap.put("/**", new ContentVersionStrategy());
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(versionStrategyMap);
List<ResourceResolver> resolvers = new ArrayList<>();
resolvers.add(new CachingResourceResolver(this.cache));
resolvers.add(new GzipResourceResolver());
resolvers.add(versionResolver);
resolvers.add(new PathResourceResolver());
this.resolver = new DefaultResourceResolverChain(resolvers);
this.locations = new ArrayList<>();
this.locations.add(new ClassPathResource("test/", getClass()));
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
this.request = MockServerHttpRequest.get("").build();
}
@Test
public void resolveGzippedFile() throws IOException {
this.request = MockServerHttpRequest.get("").header("Accept-Encoding", "gzip").build();
String file = "js/foo.js";
Resource resolved = this.resolver.resolveResource(createExchange(), file, this.locations).blockMillis(5000);
String gzFile = file+".gz";
Resource resource = new ClassPathResource("test/" + gzFile, getClass());
assertEquals(resource.getDescription(), resolved.getDescription());
assertEquals(new ClassPathResource("test/" + file).getFilename(), resolved.getFilename());
assertTrue("Expected " + resolved + " to be of type " + HttpResource.class,
resolved instanceof HttpResource);
}
@Test
public void resolveFingerprintedGzippedFile() throws IOException {
this.request = MockServerHttpRequest.get("").header("Accept-Encoding", "gzip").build();
String file = "foo-e36d2e05253c6c7085a91522ce43a0b4.css";
Resource resolved = this.resolver.resolveResource(createExchange(), file, this.locations).blockMillis(5000);
String gzFile = file + ".gz";
Resource resource = new ClassPathResource("test/" + gzFile, getClass());
assertEquals(resource.getDescription(), resolved.getDescription());
assertEquals(new ClassPathResource("test/"+file).getFilename(), resolved.getFilename());
assertTrue("Expected " + resolved + " to be of type " + HttpResource.class,
resolved instanceof HttpResource);
}
@Test
public void resolveFromCacheWithEncodingVariants() throws IOException {
this.request = MockServerHttpRequest.get("").header("Accept-Encoding", "gzip").build();
String file = "js/foo.js";
Resource resolved = this.resolver.resolveResource(createExchange(), file, this.locations).blockMillis(5000);
String gzFile = file+".gz";
Resource gzResource = new ClassPathResource("test/"+gzFile, getClass());
assertEquals(gzResource.getDescription(), resolved.getDescription());
assertEquals(new ClassPathResource("test/" + file).getFilename(), resolved.getFilename());
assertTrue("Expected " + resolved + " to be of type " + HttpResource.class,
resolved instanceof HttpResource);
// resolved resource is now cached in CachingResourceResolver
this.request = MockServerHttpRequest.get("/js/foo.js").build();
resolved = this.resolver.resolveResource(createExchange(), file, this.locations).blockMillis(5000);
Resource resource = new ClassPathResource("test/"+file, getClass());
assertEquals(resource.getDescription(), resolved.getDescription());
assertEquals(new ClassPathResource("test/" + file).getFilename(), resolved.getFilename());
assertFalse("Expected " + resolved + " to *not* be of type " + HttpResource.class,
resolved instanceof HttpResource);
}
@Test // SPR-13149
public void resolveWithNullRequest() throws IOException {
String file = "js/foo.js";
Resource resolved = this.resolver.resolveResource(null, file, this.locations).blockMillis(5000);
String gzFile = file+".gz";
Resource gzResource = new ClassPathResource("test/" + gzFile, getClass());
assertEquals(gzResource.getDescription(), resolved.getDescription());
assertEquals(new ClassPathResource("test/" + file).getFilename(), resolved.getFilename());
assertTrue("Expected " + resolved + " to be of type " + HttpResource.class,
resolved instanceof HttpResource);
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.web.reactive.resource;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Unit tests for {@link PathResourceResolver}.
* @author Rossen Stoyanchev
*/
public class PathResourceResolverTests {
private final PathResourceResolver resolver = new PathResourceResolver();
@Test
public void resolveFromClasspath() throws IOException {
Resource location = new ClassPathResource("test/", PathResourceResolver.class);
String path = "bar.css";
List<Resource> locations = singletonList(location);
Resource actual = this.resolver.resolveResource(null, path, locations, null).blockMillis(5000);
assertEquals(location.createRelative(path), actual);
}
@Test
public void resolveFromClasspathRoot() throws IOException {
Resource location = new ClassPathResource("/");
String path = "org/springframework/web/reactive/resource/test/bar.css";
List<Resource> locations = singletonList(location);
Resource actual = this.resolver.resolveResource(null, path, locations, null).blockMillis(5000);
assertNotNull(actual);
}
@Test
public void checkResource() throws IOException {
Resource location = new ClassPathResource("test/", PathResourceResolver.class);
testCheckResource(location, "../testsecret/secret.txt");
testCheckResource(location, "test/../../testsecret/secret.txt");
location = new UrlResource(getClass().getResource("./test/"));
String secretPath = new UrlResource(getClass().getResource("testsecret/secret.txt")).getURL().getPath();
testCheckResource(location, "file:" + secretPath);
testCheckResource(location, "/file:" + secretPath);
testCheckResource(location, "/" + secretPath);
testCheckResource(location, "////../.." + secretPath);
testCheckResource(location, "/%2E%2E/testsecret/secret.txt");
testCheckResource(location, "/%2e%2e/testsecret/secret.txt");
testCheckResource(location, " " + secretPath);
testCheckResource(location, "/ " + secretPath);
testCheckResource(location, "url:" + secretPath);
}
private void testCheckResource(Resource location, String requestPath) throws IOException {
List<Resource> locations = singletonList(location);
Resource actual = this.resolver.resolveResource(null, requestPath, locations, null).blockMillis(5000);
if (!location.createRelative(requestPath).exists() && !requestPath.contains(":")) {
fail(requestPath + " doesn't actually exist as a relative path");
}
assertNull(actual);
}
@Test
public void checkResourceWithAllowedLocations() {
this.resolver.setAllowedLocations(
new ClassPathResource("test/", PathResourceResolver.class),
new ClassPathResource("testalternatepath/", PathResourceResolver.class)
);
Resource location = new ClassPathResource("test/main.css", PathResourceResolver.class);
String actual = this.resolver.resolveUrlPath("../testalternatepath/bar.css",
singletonList(location), null).blockMillis(5000);
assertEquals("../testalternatepath/bar.css", actual);
}
@Test // SPR-12624
public void checkRelativeLocation() throws Exception {
String locationUrl= new UrlResource(getClass().getResource("./test/")).getURL().toExternalForm();
Resource location = new UrlResource(locationUrl.replace("/springframework","/../org/springframework"));
List<Resource> locations = singletonList(location);
assertNotNull(this.resolver.resolveResource(null, "main.css", locations, null).blockMillis(5000));
}
@Test // SPR-12747
public void checkFileLocation() throws Exception {
Resource resource = new ClassPathResource("test/main.css", PathResourceResolver.class);
assertTrue(this.resolver.checkResource(resource, resource));
}
@Test // SPR-13241
public void resolvePathRootResource() throws Exception {
Resource webjarsLocation = new ClassPathResource("/META-INF/resources/webjars/", PathResourceResolver.class);
String path = this.resolver.resolveUrlPathInternal(
"", singletonList(webjarsLocation), null).blockMillis(5000);
assertNull(path);
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.web.reactive.resource;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@code ResourceTransformerSupport}.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
*/
public class ResourceTransformerSupportTests {
private ResourceTransformerChain transformerChain;
private TestResourceTransformerSupport transformer;
private MockServerHttpRequest request;
@Before
public void setUp() {
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
PathResourceResolver pathResolver = new PathResourceResolver();
pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass()));
List<ResourceResolver> resolvers = Arrays.asList(versionResolver, pathResolver);
this.transformerChain = new DefaultResourceTransformerChain(new DefaultResourceResolverChain(resolvers), null);
this.transformer = new TestResourceTransformerSupport();
this.transformer.setResourceUrlProvider(createResourceUrlProvider(resolvers));
this.request = MockServerHttpRequest.get("").build();
}
private ResourceUrlProvider createResourceUrlProvider(List<ResourceResolver> resolvers) {
ResourceWebHandler handler = new ResourceWebHandler();
handler.setLocations(Collections.singletonList(new ClassPathResource("test/", getClass())));
handler.setResourceResolvers(resolvers);
ResourceUrlProvider urlProvider = new ResourceUrlProvider();
urlProvider.setHandlerMap(Collections.singletonMap("/resources/**", handler));
return urlProvider;
}
@Test
public void resolveUrlPath() throws Exception {
this.request = MockServerHttpRequest.get("/resources/main.css").build();
String resourcePath = "/resources/bar.css";
Resource css = new ClassPathResource("test/main.css", getClass());
String actual = this.transformer.resolveUrlPath(
resourcePath, createExchange(), css, this.transformerChain).blockMillis(5000);
assertEquals("/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
assertEquals("/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
}
@Test
public void resolveUrlPathWithRelativePath() throws Exception {
Resource css = new ClassPathResource("test/main.css", getClass());
String actual = this.transformer.resolveUrlPath(
"bar.css", createExchange(), css, this.transformerChain).blockMillis(5000);
assertEquals("bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
}
@Test
public void resolveUrlPathWithRelativePathInParentDirectory() throws Exception {
Resource imagePng = new ClassPathResource("test/images/image.png", getClass());
String actual = this.transformer.resolveUrlPath(
"../bar.css", createExchange(), imagePng, this.transformerChain).blockMillis(5000);
assertEquals("../bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
private static class TestResourceTransformerSupport extends ResourceTransformerSupport {
@Override
public Mono<Resource> transform(ServerWebExchange exchange, Resource resource,
ResourceTransformerChain chain) {
return Mono.error(new IllegalStateException("Should never be called"));
}
}
}

View File

@@ -0,0 +1,165 @@
/*
* 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.web.reactive.resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
/**
* Unit tests for {@link ResourceUrlProvider}.
*
* @author Rossen Stoyanchev
*/
public class ResourceUrlProviderTests {
private final List<Resource> locations = new ArrayList<>();
private final ResourceWebHandler handler = new ResourceWebHandler();
private final Map<String, ResourceWebHandler> handlerMap = new HashMap<>();
private final ResourceUrlProvider urlProvider = new ResourceUrlProvider();
@Before
public void setUp() throws Exception {
this.locations.add(new ClassPathResource("test/", getClass()));
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
this.handler.setLocations(locations);
this.handler.afterPropertiesSet();
this.handlerMap.put("/resources/**", this.handler);
this.urlProvider.setHandlerMap(this.handlerMap);
}
@Test
public void getStaticResourceUrl() {
String url = this.urlProvider.getForLookupPath("/resources/foo.css").blockMillis(5000);
assertEquals("/resources/foo.css", url);
}
@Test // SPR-13374
public void getStaticResourceUrlRequestWithQueryOrHash() {
MockServerHttpRequest request = MockServerHttpRequest.get("/").build();
MockServerHttpResponse response = new MockServerHttpResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(request, response);
String url = "/resources/foo.css?foo=bar&url=http://example.org";
String resolvedUrl = this.urlProvider.getForRequestUrl(exchange, url).blockMillis(5000);
assertEquals(url, resolvedUrl);
url = "/resources/foo.css#hash";
resolvedUrl = this.urlProvider.getForRequestUrl(exchange, url).blockMillis(5000);
assertEquals(url, resolvedUrl);
}
@Test
public void getFingerprintedResourceUrl() {
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
versionStrategyMap.put("/**", new ContentVersionStrategy());
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(versionStrategyMap);
List<ResourceResolver> resolvers = new ArrayList<>();
resolvers.add(versionResolver);
resolvers.add(new PathResourceResolver());
this.handler.setResourceResolvers(resolvers);
String url = this.urlProvider.getForLookupPath("/resources/foo.css").blockMillis(5000);
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url);
}
@Test // SPR-12647
public void bestPatternMatch() throws Exception {
ResourceWebHandler otherHandler = new ResourceWebHandler();
otherHandler.setLocations(this.locations);
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
versionStrategyMap.put("/**", new ContentVersionStrategy());
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(versionStrategyMap);
List<ResourceResolver> resolvers = new ArrayList<>();
resolvers.add(versionResolver);
resolvers.add(new PathResourceResolver());
otherHandler.setResourceResolvers(resolvers);
this.handlerMap.put("/resources/*.css", otherHandler);
this.urlProvider.setHandlerMap(this.handlerMap);
String url = this.urlProvider.getForLookupPath("/resources/foo.css").blockMillis(5000);
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url);
}
@Test // SPR-12592
public void initializeOnce() throws Exception {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
context.register(HandlerMappingConfiguration.class);
context.refresh();
ResourceUrlProvider urlProviderBean = context.getBean(ResourceUrlProvider.class);
assertThat(urlProviderBean.getHandlerMap(), Matchers.hasKey("/resources/**"));
assertFalse(urlProviderBean.isAutodetect());
}
@Configuration
@SuppressWarnings({"unused", "WeakerAccess"})
static class HandlerMappingConfiguration {
@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
ResourceWebHandler handler = new ResourceWebHandler();
HashMap<String, ResourceWebHandler> handlerMap = new HashMap<>();
handlerMap.put("/resources/**", handler);
SimpleUrlHandlerMapping hm = new SimpleUrlHandlerMapping();
hm.setUrlMap(handlerMap);
return hm;
}
@Bean
public ResourceUrlProvider resourceUrlProvider() {
return new ResourceUrlProvider();
}
}
}

View File

@@ -0,0 +1,603 @@
/*
* 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.web.reactive.resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
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.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.server.MethodNotAllowedException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.springframework.web.reactive.HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE;
/**
* Unit tests for {@link ResourceWebHandler}.
*
* @author Rossen Stoyanchev
*/
public class ResourceWebHandlerTests {
private ResourceWebHandler handler;
private MockServerHttpRequest request;
private MockServerHttpResponse response;
private DataBufferFactory bufferFactory = new DefaultDataBufferFactory();
@Before
public void setUp() throws Exception {
List<Resource> paths = new ArrayList<>(2);
paths.add(new ClassPathResource("test/", getClass()));
paths.add(new ClassPathResource("testalternatepath/", getClass()));
paths.add(new ClassPathResource("META-INF/resources/webjars/"));
this.handler = new ResourceWebHandler();
this.handler.setLocations(paths);
this.handler.setCacheControl(CacheControl.maxAge(3600, TimeUnit.SECONDS));
this.handler.afterPropertiesSet();
this.handler.afterSingletonsInstantiated();
this.request = MockServerHttpRequest.get("").build();
this.response = new MockServerHttpResponse();
}
@Test
public void getResource() throws Exception {
ServerWebExchange exchange = createExchange("foo.css");
this.handler.handle(exchange).blockMillis(5000);
HttpHeaders headers = this.response.getHeaders();
assertEquals(MediaType.parseMediaType("text/css"), headers.getContentType());
assertEquals(17, headers.getContentLength());
assertEquals("max-age=3600", headers.getCacheControl());
assertTrue(headers.containsKey("Last-Modified"));
assertEquals(headers.getLastModified() / 1000, resourceLastModifiedDate("test/foo.css") / 1000);
assertEquals("bytes", headers.getFirst("Accept-Ranges"));
assertEquals(1, headers.get("Accept-Ranges").size());
assertResponseBody("h1 { color:red; }");
}
@Test
public void getResourceHttpHeader() throws Exception {
this.request = MockServerHttpRequest.head("").build();
ServerWebExchange exchange = createExchange("foo.css");
this.handler.handle(exchange).blockMillis(5000);
assertNull(this.response.getStatusCode());
HttpHeaders headers = this.response.getHeaders();
assertEquals(MediaType.parseMediaType("text/css"), headers.getContentType());
assertEquals(17, headers.getContentLength());
assertEquals("max-age=3600", headers.getCacheControl());
assertTrue(headers.containsKey("Last-Modified"));
assertEquals(headers.getLastModified() / 1000, resourceLastModifiedDate("test/foo.css") / 1000);
assertEquals("bytes", headers.getFirst("Accept-Ranges"));
assertEquals(1, headers.get("Accept-Ranges").size());
assertNull(this.response.getBody());
}
@Test
public void getResourceHttpOptions() throws Exception {
this.request = MockServerHttpRequest.options("").build();
ServerWebExchange exchange = createExchange("foo.css");
this.handler.handle(exchange).blockMillis(5000);
assertNull(this.response.getStatusCode());
assertEquals("GET,HEAD,OPTIONS", this.response.getHeaders().getFirst("Allow"));
}
@Test
public void getResourceNoCache() throws Exception {
ServerWebExchange exchange = createExchange("foo.css");
this.handler.setCacheControl(CacheControl.noStore());
this.handler.handle(exchange).blockMillis(5000);
assertEquals("no-store", this.response.getHeaders().getCacheControl());
assertTrue(this.response.getHeaders().containsKey("Last-Modified"));
assertEquals(this.response.getHeaders().getLastModified() / 1000, resourceLastModifiedDate("test/foo.css") / 1000);
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
}
@Test
public void getVersionedResource() throws Exception {
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.addFixedVersionStrategy("versionString", "/**");
this.handler.setResourceResolvers(Arrays.asList(versionResolver, new PathResourceResolver()));
this.handler.afterPropertiesSet();
this.handler.afterSingletonsInstantiated();
ServerWebExchange exchange = createExchange("versionString/foo.css");
this.handler.handle(exchange).blockMillis(5000);
assertEquals("\"versionString\"", this.response.getHeaders().getETag());
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
}
@Test
public void getResourceWithHtmlMediaType() throws Exception {
ServerWebExchange exchange = createExchange("foo.html");
this.handler.handle(exchange).blockMillis(5000);
HttpHeaders headers = this.response.getHeaders();
assertEquals(MediaType.TEXT_HTML, headers.getContentType());
assertEquals("max-age=3600", headers.getCacheControl());
assertTrue(headers.containsKey("Last-Modified"));
assertEquals(headers.getLastModified() / 1000, resourceLastModifiedDate("test/foo.html") / 1000);
assertEquals("bytes", headers.getFirst("Accept-Ranges"));
assertEquals(1, headers.get("Accept-Ranges").size());
}
@Test
public void getResourceFromAlternatePath() throws Exception {
ServerWebExchange exchange = createExchange("baz.css");
this.handler.handle(exchange).blockMillis(5000);
HttpHeaders headers = this.response.getHeaders();
assertEquals(MediaType.parseMediaType("text/css"), headers.getContentType());
assertEquals(17, headers.getContentLength());
assertEquals("max-age=3600", headers.getCacheControl());
assertTrue(headers.containsKey("Last-Modified"));
assertEquals(headers.getLastModified() / 1000, resourceLastModifiedDate("testalternatepath/baz.css") / 1000);
assertEquals("bytes", headers.getFirst("Accept-Ranges"));
assertEquals(1, headers.get("Accept-Ranges").size());
assertResponseBody("h1 { color:red; }");
}
@Test
public void getResourceFromSubDirectory() throws Exception {
ServerWebExchange exchange = createExchange("js/foo.js");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(MediaType.parseMediaType("text/javascript"), this.response.getHeaders().getContentType());
assertResponseBody("function foo() { console.log(\"hello world\"); }");
}
@Test
public void getResourceFromSubDirectoryOfAlternatePath() throws Exception {
ServerWebExchange exchange = createExchange("js/baz.js");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(MediaType.parseMediaType("text/javascript"), this.response.getHeaders().getContentType());
assertResponseBody("function foo() { console.log(\"hello world\"); }");
}
@Test // SPR-13658
public void getResourceWithRegisteredMediaType() throws Exception {
CompositeContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder()
.mediaType("css", new MediaType("foo", "bar"))
.build();
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
ResourceWebHandler handler = new ResourceWebHandler();
handler.setLocations(paths);
handler.setContentTypeResolver(contentTypeResolver);
handler.afterPropertiesSet();
handler.afterSingletonsInstantiated();
ServerWebExchange exchange = createExchange("foo.css");
handler.handle(exchange).blockMillis(5000);
assertEquals(MediaType.parseMediaType("foo/bar"), this.response.getHeaders().getContentType());
assertResponseBody("h1 { color:red; }");
}
@Test // SPR-14577
public void getMediaTypeWithFavorPathExtensionOff() throws Exception {
CompositeContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder()
.favorPathExtension(false)
.build();
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
ResourceWebHandler handler = new ResourceWebHandler();
handler.setLocations(paths);
handler.setContentTypeResolver(contentTypeResolver);
handler.afterPropertiesSet();
handler.afterSingletonsInstantiated();
this.request = MockServerHttpRequest.get("").header("Accept", "application/json,text/plain,*/*").build();
ServerWebExchange exchange = createExchange("foo.html");
handler.handle(exchange).blockMillis(5000);
assertEquals(MediaType.TEXT_HTML, this.response.getHeaders().getContentType());
}
@Test
public void invalidPath() throws Exception {
for (HttpMethod method : HttpMethod.values()) {
testInvalidPath(method);
}
}
private void testInvalidPath(HttpMethod httpMethod) throws Exception {
Resource location = new ClassPathResource("test/", getClass());
this.handler.setLocations(Collections.singletonList(location));
testInvalidPath(httpMethod, "../testsecret/secret.txt", location);
testInvalidPath(httpMethod, "test/../../testsecret/secret.txt", location);
testInvalidPath(httpMethod, ":/../../testsecret/secret.txt", location);
location = new UrlResource(getClass().getResource("./test/"));
this.handler.setLocations(Collections.singletonList(location));
Resource secretResource = new UrlResource(getClass().getResource("testsecret/secret.txt"));
String secretPath = secretResource.getURL().getPath();
testInvalidPath(httpMethod, "file:" + secretPath, location);
testInvalidPath(httpMethod, "/file:" + secretPath, location);
testInvalidPath(httpMethod, "url:" + secretPath, location);
testInvalidPath(httpMethod, "/url:" + secretPath, location);
testInvalidPath(httpMethod, "////../.." + secretPath, location);
testInvalidPath(httpMethod, "/%2E%2E/testsecret/secret.txt", location);
testInvalidPath(httpMethod, "url:" + secretPath, location);
// The following tests fail with a MalformedURLException on Windows
// testInvalidPath(location, "/" + secretPath);
// testInvalidPath(location, "/ " + secretPath);
}
private void testInvalidPath(HttpMethod httpMethod, String requestPath, Resource location) throws Exception {
this.request = MockServerHttpRequest.method(httpMethod, "").build();
this.response = new MockServerHttpResponse();
ServerWebExchange exchange = createExchange(requestPath);
this.handler.handle(exchange).blockMillis(5000);
if (!location.createRelative(requestPath).exists() && !requestPath.contains(":")) {
fail(requestPath + " doesn't actually exist as a relative path");
}
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
}
@Test
public void ignoreInvalidEscapeSequence() throws Exception {
ServerWebExchange exchange = createExchange("/%foo%/bar.txt");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
}
@Test
public void processPath() throws Exception {
assertSame("/foo/bar", this.handler.processPath("/foo/bar"));
assertSame("foo/bar", this.handler.processPath("foo/bar"));
// leading whitespace control characters (00-1F)
assertEquals("/foo/bar", this.handler.processPath(" /foo/bar"));
assertEquals("/foo/bar", this.handler.processPath((char) 1 + "/foo/bar"));
assertEquals("/foo/bar", this.handler.processPath((char) 31 + "/foo/bar"));
assertEquals("foo/bar", this.handler.processPath(" foo/bar"));
assertEquals("foo/bar", this.handler.processPath((char) 31 + "foo/bar"));
// leading control character 0x7F (DEL)
assertEquals("/foo/bar", this.handler.processPath((char) 127 + "/foo/bar"));
assertEquals("/foo/bar", this.handler.processPath((char) 127 + "/foo/bar"));
// leading control and '/' characters
assertEquals("/foo/bar", this.handler.processPath(" / foo/bar"));
assertEquals("/foo/bar", this.handler.processPath(" / / foo/bar"));
assertEquals("/foo/bar", this.handler.processPath(" // /// //// foo/bar"));
assertEquals("/foo/bar", this.handler.processPath((char) 1 + " / " + (char) 127 + " // foo/bar"));
// root or empty path
assertEquals("", this.handler.processPath(" "));
assertEquals("/", this.handler.processPath("/"));
assertEquals("/", this.handler.processPath("///"));
assertEquals("/", this.handler.processPath("/ / / "));
}
@Test
public void initAllowedLocations() throws Exception {
PathResourceResolver resolver = (PathResourceResolver) this.handler.getResourceResolvers().get(0);
Resource[] locations = resolver.getAllowedLocations();
assertEquals(3, locations.length);
assertEquals("test/", ((ClassPathResource) locations[0]).getPath());
assertEquals("testalternatepath/", ((ClassPathResource) locations[1]).getPath());
assertEquals("META-INF/resources/webjars/", ((ClassPathResource) locations[2]).getPath());
}
@Test
public void initAllowedLocationsWithExplicitConfiguration() throws Exception {
ClassPathResource location1 = new ClassPathResource("test/", getClass());
ClassPathResource location2 = new ClassPathResource("testalternatepath/", getClass());
PathResourceResolver pathResolver = new PathResourceResolver();
pathResolver.setAllowedLocations(location1);
ResourceWebHandler handler = new ResourceWebHandler();
handler.setResourceResolvers(Collections.singletonList(pathResolver));
handler.setLocations(Arrays.asList(location1, location2));
handler.afterPropertiesSet();
handler.afterSingletonsInstantiated();
Resource[] locations = pathResolver.getAllowedLocations();
assertEquals(1, locations.length);
assertEquals("test/", ((ClassPathResource) locations[0]).getPath());
}
@Test
public void notModified() throws Exception {
this.request = MockServerHttpRequest.get("").ifModifiedSince(resourceLastModified("test/foo.css")).build();
ServerWebExchange exchange = createExchange("foo.css");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.NOT_MODIFIED, this.response.getStatusCode());
}
@Test
public void modified() throws Exception {
long timestamp = resourceLastModified("test/foo.css") / 1000 * 1000 - 1;
this.request = MockServerHttpRequest.get("").ifModifiedSince(timestamp).build();
ServerWebExchange exchange = createExchange("foo.css");
this.handler.handle(exchange).blockMillis(5000);
assertNull(this.response.getStatusCode());
assertResponseBody("h1 { color:red; }");
}
@Test
public void directory() throws Exception {
ServerWebExchange exchange = createExchange("js/");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
}
@Test
public void directoryInJarFile() throws Exception {
ServerWebExchange exchange = createExchange("underscorejs/");
this.handler.handle(exchange).blockMillis(5000);
assertNull(this.response.getStatusCode());
assertEquals(0, this.response.getHeaders().getContentLength());
}
@Test
public void missingResourcePath() throws Exception {
ServerWebExchange exchange = createExchange("");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
}
@Test(expected = IllegalStateException.class)
public void noPathWithinHandlerMappingAttribute() throws Exception {
ServerWebExchange exchange = new DefaultServerWebExchange(this.request, this.response);
this.handler.handle(exchange).blockMillis(5000);
}
@Test(expected = MethodNotAllowedException.class)
public void unsupportedHttpMethod() throws Exception {
this.request = MockServerHttpRequest.post("").build();
ServerWebExchange exchange = createExchange("foo.css");
this.handler.handle(exchange).blockMillis(5000);
}
@Test
public void resourceNotFound() throws Exception {
for (HttpMethod method : HttpMethod.values()) {
resourceNotFound(method);
}
}
private void resourceNotFound(HttpMethod httpMethod) throws Exception {
this.request = MockServerHttpRequest.method(httpMethod, "").build();
this.response = new MockServerHttpResponse();
ServerWebExchange exchange = createExchange("not-there.css");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
}
@Test
public void partialContentByteRange() throws Exception {
this.request = MockServerHttpRequest.get("").header("Range", "bytes=0-1").build();
ServerWebExchange exchange = createExchange("foo.txt");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
assertEquals(MediaType.TEXT_PLAIN, this.response.getHeaders().getContentType());
assertEquals(2, this.response.getHeaders().getContentLength());
assertEquals("bytes 0-1/10", this.response.getHeaders().getFirst("Content-Range"));
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
assertResponseBody("So");
}
@Test
public void partialContentByteRangeNoEnd() throws Exception {
this.request = MockServerHttpRequest.get("").header("range", "bytes=9-").build();
ServerWebExchange exchange = createExchange("foo.txt");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
assertEquals(MediaType.TEXT_PLAIN, this.response.getHeaders().getContentType());
assertEquals(1, this.response.getHeaders().getContentLength());
assertEquals("bytes 9-9/10", this.response.getHeaders().getFirst("Content-Range"));
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
assertResponseBody(".");
}
@Test
public void partialContentByteRangeLargeEnd() throws Exception {
this.request = MockServerHttpRequest.get("").header("range", "bytes=9-10000").build();
ServerWebExchange exchange = createExchange("foo.txt");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
assertEquals(MediaType.TEXT_PLAIN, this.response.getHeaders().getContentType());
assertEquals(1, this.response.getHeaders().getContentLength());
assertEquals("bytes 9-9/10", this.response.getHeaders().getFirst("Content-Range"));
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
assertResponseBody(".");
}
@Test
public void partialContentSuffixRange() throws Exception {
this.request = MockServerHttpRequest.get("").header("range", "bytes=-1").build();
ServerWebExchange exchange = createExchange("foo.txt");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
assertEquals(MediaType.TEXT_PLAIN, this.response.getHeaders().getContentType());
assertEquals(1, this.response.getHeaders().getContentLength());
assertEquals("bytes 9-9/10", this.response.getHeaders().getFirst("Content-Range"));
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
assertResponseBody(".");
}
@Test
public void partialContentSuffixRangeLargeSuffix() throws Exception {
this.request = MockServerHttpRequest.get("").header("range", "bytes=-11").build();
ServerWebExchange exchange = createExchange("foo.txt");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
assertEquals(MediaType.TEXT_PLAIN, this.response.getHeaders().getContentType());
assertEquals(10, this.response.getHeaders().getContentLength());
assertEquals("bytes 0-9/10", this.response.getHeaders().getFirst("Content-Range"));
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
assertResponseBody("Some text.");
}
@Test
public void partialContentInvalidRangeHeader() throws Exception {
this.request = MockServerHttpRequest.get("").header("range", "bytes=foo bar").build();
ServerWebExchange exchange = createExchange("foo.txt");
StepVerifier.create(this.handler.handle(exchange))
.expectNextCount(0)
.expectComplete()
.verify();
assertEquals(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE, this.response.getStatusCode());
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
}
@Test
public void partialContentMultipleByteRanges() throws Exception {
this.request = MockServerHttpRequest.get("").header("Range", "bytes=0-1, 4-5, 8-9").build();
ServerWebExchange exchange = createExchange("foo.txt");
this.handler.handle(exchange).blockMillis(5000);
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
assertTrue(this.response.getHeaders().getContentType().toString()
.startsWith("multipart/byteranges;boundary="));
String boundary = "--" + this.response.getHeaders().getContentType().toString().substring(30);
Mono<DataBuffer> reduced = Flux.from(this.response.getBody())
.reduce(this.bufferFactory.allocateBuffer(), (previous, current) -> {
previous.write(current);
DataBufferUtils.release(current);
return previous;
});
StepVerifier.create(reduced)
.consumeNextWith(buf -> {
String content = DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8);
String[] ranges = StringUtils.tokenizeToStringArray(content, "\r\n", false, true);
assertEquals(boundary, ranges[0]);
assertEquals("Content-Type: text/plain", ranges[1]);
assertEquals("Content-Range: bytes 0-1/10", ranges[2]);
assertEquals("So", ranges[3]);
assertEquals(boundary, ranges[4]);
assertEquals("Content-Type: text/plain", ranges[5]);
assertEquals("Content-Range: bytes 4-5/10", ranges[6]);
assertEquals(" t", ranges[7]);
assertEquals(boundary, ranges[8]);
assertEquals("Content-Type: text/plain", ranges[9]);
assertEquals("Content-Range: bytes 8-9/10", ranges[10]);
assertEquals("t.", ranges[11]);
})
.expectComplete()
.verify();
}
@Test // SPR-14005
public void doOverwriteExistingCacheControlHeaders() throws Exception {
this.response.getHeaders().setCacheControl(CacheControl.noStore().getHeaderValue());
ServerWebExchange exchange = createExchange("foo.css");
this.handler.handle(exchange).blockMillis(5000);
assertEquals("max-age=3600", this.response.getHeaders().getCacheControl());
}
@NotNull
private ServerWebExchange createExchange(String path) {
ServerWebExchange exchange = new DefaultServerWebExchange(this.request, this.response);
exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, path);
return exchange;
}
private long resourceLastModified(String resourceName) throws IOException {
return new ClassPathResource(resourceName, getClass()).getFile().lastModified();
}
private long resourceLastModifiedDate(String resourceName) throws IOException {
return new ClassPathResource(resourceName, getClass()).getFile().lastModified();
}
private void assertResponseBody(String responseBody) {
StepVerifier.create(this.response.getBody())
.consumeNextWith(buf -> assertEquals(responseBody,
DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8)))
.expectComplete()
.verify();
}
}

View File

@@ -0,0 +1,222 @@
/*
* 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.web.reactive.resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.never;
import static org.mockito.BDDMockito.times;
import static org.mockito.BDDMockito.verify;
/**
* Unit tests for {@link VersionResourceResolver}.
* @author Rossen Stoyanchev
* @author Brian Clozel
*/
public class VersionResourceResolverTests {
private List<Resource> locations;
private VersionResourceResolver resolver;
private ResourceResolverChain chain;
private VersionStrategy versionStrategy;
@Before
public void setup() {
this.locations = new ArrayList<>();
this.locations.add(new ClassPathResource("test/", getClass()));
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
this.resolver = new VersionResourceResolver();
this.chain = mock(ResourceResolverChain.class);
this.versionStrategy = mock(VersionStrategy.class);
}
@Test
public void resolveResourceExisting() throws Exception {
String file = "bar.css";
Resource expected = new ClassPathResource("test/" + file, getClass());
given(this.chain.resolveResource(null, file, this.locations)).willReturn(Mono.just(expected));
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
Resource actual = this.resolver
.resolveResourceInternal(null, file, this.locations, this.chain)
.blockMillis(5000);
assertEquals(expected, actual);
verify(this.chain, times(1)).resolveResource(null, file, this.locations);
verify(this.versionStrategy, never()).extractVersion(file);
}
@Test
public void resolveResourceNoVersionStrategy() throws Exception {
String file = "missing.css";
given(this.chain.resolveResource(null, file, this.locations)).willReturn(Mono.empty());
this.resolver.setStrategyMap(Collections.emptyMap());
Resource actual = this.resolver
.resolveResourceInternal(null, file, this.locations, this.chain)
.blockMillis(5000);
assertNull(actual);
verify(this.chain, times(1)).resolveResource(null, file, this.locations);
}
@Test
public void resolveResourceNoVersionInPath() throws Exception {
String file = "bar.css";
given(this.chain.resolveResource(null, file, this.locations)).willReturn(Mono.empty());
given(this.versionStrategy.extractVersion(file)).willReturn("");
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
Resource actual = this.resolver
.resolveResourceInternal(null, file, this.locations, this.chain)
.blockMillis(5000);
assertNull(actual);
verify(this.chain, times(1)).resolveResource(null, file, this.locations);
verify(this.versionStrategy, times(1)).extractVersion(file);
}
@Test
public void resolveResourceNoResourceAfterVersionRemoved() throws Exception {
String versionFile = "bar-version.css";
String version = "version";
String file = "bar.css";
given(this.chain.resolveResource(null, versionFile, this.locations)).willReturn(Mono.empty());
given(this.chain.resolveResource(null, file, this.locations)).willReturn(Mono.empty());
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
given(this.versionStrategy.removeVersion(versionFile, version)).willReturn(file);
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
Resource actual = this.resolver
.resolveResourceInternal(null, versionFile, this.locations, this.chain)
.blockMillis(5000);
assertNull(actual);
verify(this.versionStrategy, times(1)).removeVersion(versionFile, version);
}
@Test
public void resolveResourceVersionDoesNotMatch() throws Exception {
String versionFile = "bar-version.css";
String version = "version";
String file = "bar.css";
Resource expected = new ClassPathResource("test/" + file, getClass());
given(this.chain.resolveResource(null, versionFile, this.locations)).willReturn(Mono.empty());
given(this.chain.resolveResource(null, file, this.locations)).willReturn(Mono.just(expected));
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
given(this.versionStrategy.removeVersion(versionFile, version)).willReturn(file);
given(this.versionStrategy.getResourceVersion(expected)).willReturn("newer-version");
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
Resource actual = this.resolver
.resolveResourceInternal(null, versionFile, this.locations, this.chain)
.blockMillis(5000);
assertNull(actual);
verify(this.versionStrategy, times(1)).getResourceVersion(expected);
}
@Test
public void resolveResourceSuccess() throws Exception {
String versionFile = "bar-version.css";
String version = "version";
String file = "bar.css";
Resource expected = new ClassPathResource("test/" + file, getClass());
MockServerHttpRequest request = MockServerHttpRequest.get("/resources/bar-version.css").build();
MockServerHttpResponse response = new MockServerHttpResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(request, response);
given(this.chain.resolveResource(exchange, versionFile, this.locations)).willReturn(Mono.empty());
given(this.chain.resolveResource(exchange, file, this.locations)).willReturn(Mono.just(expected));
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
given(this.versionStrategy.removeVersion(versionFile, version)).willReturn(file);
given(this.versionStrategy.getResourceVersion(expected)).willReturn(version);
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
Resource actual = this.resolver
.resolveResourceInternal(exchange, versionFile, this.locations, this.chain)
.blockMillis(5000);
assertEquals(expected.getFilename(), actual.getFilename());
verify(this.versionStrategy, times(1)).getResourceVersion(expected);
assertThat(actual, instanceOf(HttpResource.class));
assertEquals("\"" + version + "\"", ((HttpResource)actual).getResponseHeaders().getETag());
}
@Test
public void getStrategyForPath() throws Exception {
Map<String, VersionStrategy> strategies = new HashMap<>();
VersionStrategy jsStrategy = mock(VersionStrategy.class);
VersionStrategy catchAllStrategy = mock(VersionStrategy.class);
strategies.put("/**", catchAllStrategy);
strategies.put("/**/*.js", jsStrategy);
this.resolver.setStrategyMap(strategies);
assertEquals(catchAllStrategy, this.resolver.getStrategyForPath("foo.css"));
assertEquals(catchAllStrategy, this.resolver.getStrategyForPath("foo-js.css"));
assertEquals(jsStrategy, this.resolver.getStrategyForPath("foo.js"));
assertEquals(jsStrategy, this.resolver.getStrategyForPath("bar/foo.js"));
}
@Test // SPR-13883
public void shouldConfigureFixedPrefixAutomatically() throws Exception {
this.resolver.addFixedVersionStrategy("fixedversion", "/js/**", "/css/**", "/fixedversion/css/**");
assertThat(this.resolver.getStrategyMap().size(), is(4));
assertThat(this.resolver.getStrategyForPath("js/something.js"),
Matchers.instanceOf(FixedVersionStrategy.class));
assertThat(this.resolver.getStrategyForPath("fixedversion/js/something.js"),
Matchers.instanceOf(FixedVersionStrategy.class));
assertThat(this.resolver.getStrategyForPath("css/something.css"),
Matchers.instanceOf(FixedVersionStrategy.class));
assertThat(this.resolver.getStrategyForPath("fixedversion/css/something.css"),
Matchers.instanceOf(FixedVersionStrategy.class));
}
}

View File

@@ -0,0 +1,173 @@
/*
* 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.web.reactive.resource;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.mock;
import static org.mockito.BDDMockito.never;
import static org.mockito.BDDMockito.times;
import static org.mockito.BDDMockito.verify;
/**
* Unit tests for {@link WebJarsResourceResolver}.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
*/
public class WebJarsResourceResolverTests {
private List<Resource> locations;
private WebJarsResourceResolver resolver;
private ResourceResolverChain chain;
private ServerWebExchange exchange;
@Before
public void setup() {
// for this to work, an actual WebJar must be on the test classpath
this.locations = singletonList(new ClassPathResource("/META-INF/resources/webjars"));
this.resolver = new WebJarsResourceResolver();
this.chain = mock(ResourceResolverChain.class);
MockServerHttpRequest request = MockServerHttpRequest.get("").build();
ServerHttpResponse response = new MockServerHttpResponse();
this.exchange = new DefaultServerWebExchange(request, response);
}
@Test
public void resolveUrlExisting() {
this.locations = singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
String file = "/foo/2.3/foo.txt";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.just(file));
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain).blockMillis(5000);
assertEquals(file, actual);
verify(this.chain, times(1)).resolveUrlPath(file, this.locations);
}
@Test
public void resolveUrlExistingNotInJarFile() {
this.locations = singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
String file = "foo/foo.txt";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain).blockMillis(5000);
assertNull(actual);
verify(this.chain, times(1)).resolveUrlPath(file, this.locations);
verify(this.chain, never()).resolveUrlPath("foo/2.3/foo.txt", this.locations);
}
@Test
public void resolveUrlWebJarResource() {
String file = "underscorejs/underscore.js";
String expected = "underscorejs/1.8.3/underscore.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(Mono.just(expected));
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain).blockMillis(5000);
assertEquals(expected, actual);
verify(this.chain, times(1)).resolveUrlPath(file, this.locations);
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
}
@Test
public void resolveUrlWebJarResourceNotFound() {
String file = "something/something.js";
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain).blockMillis(5000);
assertNull(actual);
verify(this.chain, times(1)).resolveUrlPath(file, this.locations);
verify(this.chain, never()).resolveUrlPath(null, this.locations);
}
@Test
public void resolveResourceExisting() {
Resource expected = mock(Resource.class);
this.locations = singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
String file = "foo/2.3/foo.txt";
given(this.chain.resolveResource(this.exchange, file, this.locations)).willReturn(Mono.just(expected));
Resource actual = this.resolver
.resolveResource(this.exchange, file, this.locations, this.chain)
.blockMillis(5000);
assertEquals(expected, actual);
verify(this.chain, times(1)).resolveResource(this.exchange, file, this.locations);
}
@Test
public void resolveResourceNotFound() {
String file = "something/something.js";
given(this.chain.resolveResource(this.exchange, file, this.locations)).willReturn(Mono.empty());
Resource actual = this.resolver
.resolveResource(this.exchange, file, this.locations, this.chain)
.blockMillis(5000);
assertNull(actual);
verify(this.chain, times(1)).resolveResource(this.exchange, file, this.locations);
verify(this.chain, never()).resolveResource(this.exchange, null, this.locations);
}
@Test
public void resolveResourceWebJar() {
this.locations = singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
String file = "underscorejs/underscore.js";
given(this.chain.resolveResource(this.exchange, file, this.locations)).willReturn(Mono.empty());
Resource expected = mock(Resource.class);
String expectedPath = "underscorejs/1.8.3/underscore.js";
given(this.chain.resolveResource(this.exchange, expectedPath, this.locations))
.willReturn(Mono.just(expected));
Resource actual = this.resolver
.resolveResource(this.exchange, file, this.locations, this.chain)
.blockMillis(5000);
assertEquals(expected, actual);
verify(this.chain, times(1)).resolveResource(this.exchange, file, this.locations);
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.web.reactive.result;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
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.MockServerHttpResponse;
import org.springframework.web.reactive.accept.FixedContentTypeResolver;
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.springframework.http.MediaType.ALL;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM;
import static org.springframework.http.MediaType.IMAGE_GIF;
import static org.springframework.http.MediaType.IMAGE_JPEG;
import static org.springframework.http.MediaType.IMAGE_PNG;
import static org.springframework.http.MediaType.TEXT_PLAIN;
import static org.springframework.web.reactive.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
/**
* Unit tests for {@link AbstractHandlerResultHandler}.
* @author Rossen Stoyanchev
*/
public class HandlerResultHandlerTests {
private TestResultHandler resultHandler;
private MockServerHttpRequest request;
@Before
public void setUp() throws Exception {
this.resultHandler = new TestResultHandler();
this.request = MockServerHttpRequest.get("/path").build();
}
@Test
public void usesContentTypeResolver() throws Exception {
TestResultHandler resultHandler = new TestResultHandler(new FixedContentTypeResolver(IMAGE_GIF));
List<MediaType> mediaTypes = Arrays.asList(IMAGE_JPEG, IMAGE_GIF, IMAGE_PNG);
MediaType actual = resultHandler.selectMediaType(exchange(), () -> mediaTypes);
assertEquals(IMAGE_GIF, actual);
}
@Test
public void producibleMediaTypesRequestAttribute() throws Exception {
ServerWebExchange exchange = exchange();
exchange.getAttributes().put(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(IMAGE_GIF));
List<MediaType> mediaTypes = Arrays.asList(IMAGE_JPEG, IMAGE_GIF, IMAGE_PNG);
MediaType actual = resultHandler.selectMediaType(exchange, () -> mediaTypes);
assertEquals(IMAGE_GIF, actual);
}
@Test // SPR-9160
public void sortsByQuality() throws Exception {
this.request = MockServerHttpRequest.get("/path")
.header("Accept", "text/plain; q=0.5, application/json")
.build();
List<MediaType> mediaTypes = Arrays.asList(TEXT_PLAIN, APPLICATION_JSON_UTF8);
MediaType actual = this.resultHandler.selectMediaType(exchange(), () -> mediaTypes);
assertEquals(APPLICATION_JSON_UTF8, actual);
}
@Test
public void charsetFromAcceptHeader() throws Exception {
MediaType text8859 = MediaType.parseMediaType("text/plain;charset=ISO-8859-1");
MediaType textUtf8 = MediaType.parseMediaType("text/plain;charset=UTF-8");
this.request = MockServerHttpRequest.get("/path").accept(text8859).build();
MediaType actual = this.resultHandler.selectMediaType(exchange(), () -> Collections.singletonList(textUtf8));
assertEquals(text8859, actual);
}
@Test // SPR-12894
public void noConcreteMediaType() throws Exception {
List<MediaType> producible = Collections.singletonList(ALL);
MediaType actual = this.resultHandler.selectMediaType(exchange(), () -> producible);
assertEquals(APPLICATION_OCTET_STREAM, actual);
}
@NotNull
private DefaultServerWebExchange exchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
@SuppressWarnings("WeakerAccess")
private static class TestResultHandler extends AbstractHandlerResultHandler {
protected TestResultHandler() {
this(new HeaderContentTypeResolver());
}
public TestResultHandler(RequestedContentTypeResolver contentTypeResolver) {
super(contentTypeResolver);
}
}
}

View File

@@ -0,0 +1,238 @@
/*
* 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.result;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
/**
* Convenience class for use in tests to resolve a {@link Method} and/or any of
* its {@link MethodParameter}s based on some hints.
*
* <p>In tests we often create a class (e.g. TestController) with diverse method
* signatures and annotations to test with. Use of descriptive method and argument
* names combined with using reflection, it becomes challenging to read and write
* tests and it becomes necessary to navigate to the actual method declaration
* which is cumbersome and involves several steps.
*
* <p>The idea here is to provide enough hints to resolving a method uniquely
* where the hints document exactly what is being tested and there is usually no
* need to navigate to the actual method declaration. For example if testing
* response handling, the return type may be used as a hint:
*
* <pre>
* ResolvableMethod resolvableMethod = ResolvableMethod.onClass(TestController.class);
* ResolvableType type = ResolvableType.forClassWithGenerics(Mono.class, View.class);
* Method method = resolvableMethod.returning(type).resolve();
*
* type = ResolvableType.forClassWithGenerics(Mono.class, String.class);
* method = resolvableMethod.returning(type).resolve();
*
* // ...
* </pre>
*
* <p>Additional {@code resolve} methods provide options to obtain one of the method
* arguments or return type as a {@link MethodParameter}.
*
* @author Rossen Stoyanchev
*/
public class ResolvableMethod {
private final Class<?> objectClass;
private final Object object;
private String methodName;
private Class<?>[] argumentTypes;
private ResolvableType returnType;
private final List<Class<? extends Annotation>> annotationTypes = new ArrayList<>(4);
private final List<Predicate<Method>> predicates = new ArrayList<>(4);
private ResolvableMethod(Class<?> objectClass) {
Assert.notNull(objectClass, "Class must not be null");
this.objectClass = objectClass;
this.object = null;
}
private ResolvableMethod(Object object) {
Assert.notNull(object, "Object must not be null");
this.object = object;
this.objectClass = object.getClass();
}
/**
* Methods that match the given name (regardless of arguments).
*/
public ResolvableMethod name(String methodName) {
this.methodName = methodName;
return this;
}
/**
* Methods that match the given argument types.
*/
public ResolvableMethod argumentTypes(Class<?>... argumentTypes) {
this.argumentTypes = argumentTypes;
return this;
}
/**
* Methods declared to return the given type.
*/
public ResolvableMethod returning(ResolvableType resolvableType) {
this.returnType = resolvableType;
return this;
}
/**
* Methods with the given annotation.
*/
public ResolvableMethod annotated(Class<? extends Annotation> annotationType) {
this.annotationTypes.add(annotationType);
return this;
}
/**
* Methods matching the given predicate.
*/
public final ResolvableMethod matching(Predicate<Method> methodPredicate) {
this.predicates.add(methodPredicate);
return this;
}
// Resolve methods
public Method resolve() {
Set<Method> methods = MethodIntrospector.selectMethods(this.objectClass,
(ReflectionUtils.MethodFilter) method -> {
if (this.methodName != null && !this.methodName.equals(method.getName())) {
return false;
}
if (getReturnType() != null) {
// String comparison (ResolvableType's with different providers)
String actual = ResolvableType.forMethodReturnType(method).toString();
if (!actual.equals(getReturnType()) && !Object.class.equals(method.getDeclaringClass())) {
return false;
}
}
else if (!ObjectUtils.isEmpty(this.argumentTypes)) {
if (!Arrays.equals(this.argumentTypes, method.getParameterTypes())) {
return false;
}
}
else if (this.annotationTypes.stream()
.filter(annotType -> AnnotationUtils.findAnnotation(method, annotType) == null)
.findFirst()
.isPresent()) {
return false;
}
else if (this.predicates.stream().filter(p -> !p.test(method)).findFirst().isPresent()) {
return false;
}
return true;
});
Assert.state(!methods.isEmpty(), () -> "No matching method: " + this);
Assert.state(methods.size() == 1, () -> "Multiple matching methods: " + this);
return methods.iterator().next();
}
private String getReturnType() {
return (this.returnType != null ? this.returnType.toString() : null);
}
public InvocableHandlerMethod resolveHandlerMethod() {
Assert.state(this.object != null, "Object must not be null");
return new InvocableHandlerMethod(this.object, resolve());
}
public MethodParameter resolveReturnType() {
Method method = resolve();
return new MethodParameter(method, -1);
}
@SafeVarargs
public final MethodParameter resolveParam(Predicate<MethodParameter>... predicates) {
return resolveParam(null, predicates);
}
@SafeVarargs
public final MethodParameter resolveParam(ResolvableType type, Predicate<MethodParameter>... predicates) {
List<MethodParameter> matches = new ArrayList<>();
Method method = resolve();
for (int i = 0; i < method.getParameterCount(); i++) {
MethodParameter param = new MethodParameter(method, i);
if (type != null) {
if (!ResolvableType.forMethodParameter(param).toString().equals(type.toString())) {
continue;
}
}
if (!ObjectUtils.isEmpty(predicates)) {
if (Arrays.stream(predicates).filter(p -> !p.test(param)).findFirst().isPresent()) {
continue;
}
}
matches.add(param);
}
Assert.state(!matches.isEmpty(), () -> "No matching arg on " + method.toString());
Assert.state(matches.size() == 1, () -> "Multiple matching args: " + matches + " on " + method.toString());
return matches.get(0);
}
@Override
public String toString() {
return "Class=" + this.objectClass +
", name=" + (this.methodName != null ? this.methodName : "<not specified>") +
", returnType=" + (this.returnType != null ? this.returnType : "<not specified>") +
", annotations=" + this.annotationTypes;
}
public static ResolvableMethod onClass(Class<?> clazz) {
return new ResolvableMethod(clazz);
}
public static ResolvableMethod on(Object object) {
return new ResolvableMethod(object);
}
}

View File

@@ -0,0 +1,145 @@
/*
* 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.web.reactive.result;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* Integration tests with requests mapped via
* {@link SimpleUrlHandlerMapping} to plain {@link WebHandler}s.
*
* @author Rossen Stoyanchev
*/
public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@Override
protected HttpHandler createHttpHandler() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
return WebHttpHandlerBuilder.webHandler(new DispatcherHandler(wac))
.exceptionHandlers(new ResponseStatusExceptionHandler())
.build();
}
@Test
public void testRequestToFooHandler() throws Exception {
URI url = new URI("http://localhost:" + this.port + "/foo");
RequestEntity<Void> request = RequestEntity.get(url).build();
ResponseEntity<byte[]> response = new RestTemplate().exchange(request, byte[].class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertArrayEquals("foo".getBytes("UTF-8"), response.getBody());
}
@Test
public void testRequestToBarHandler() throws Exception {
URI url = new URI("http://localhost:" + this.port + "/bar");
RequestEntity<Void> request = RequestEntity.get(url).build();
ResponseEntity<byte[]> response = new RestTemplate().exchange(request, byte[].class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertArrayEquals("bar".getBytes("UTF-8"), response.getBody());
}
@Test
public void testRequestToHeaderSettingHandler() throws Exception {
URI url = new URI("http://localhost:" + this.port + "/header");
RequestEntity<Void> request = RequestEntity.get(url).build();
ResponseEntity<byte[]> response = new RestTemplate().exchange(request, byte[].class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("bar", response.getHeaders().getFirst("foo"));
}
@Test
public void testHandlerNotFound() throws Exception {
URI url = new URI("http://localhost:" + this.port + "/oops");
RequestEntity<Void> request = RequestEntity.get(url).build();
try {
new RestTemplate().exchange(request, byte[].class);
}
catch (HttpClientErrorException ex) {
assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
}
}
private static DataBuffer asDataBuffer(String text) {
DefaultDataBuffer buffer = new DefaultDataBufferFactory().allocateBuffer();
return buffer.write(text.getBytes(StandardCharsets.UTF_8));
}
@Configuration
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig {
@Bean
public SimpleUrlHandlerMapping handlerMapping() {
return new SimpleUrlHandlerMapping() {
{
Map<String, Object> map = new HashMap<>();
map.put("/foo", (WebHandler) exchange ->
exchange.getResponse().writeWith(Flux.just(asDataBuffer("foo"))));
map.put("/bar", (WebHandler) exchange ->
exchange.getResponse().writeWith(Flux.just(asDataBuffer("bar"))));
map.put("/header", (WebHandler) exchange -> {
exchange.getResponse().getHeaders().add("foo", "bar");
return Mono.empty();
});
setUrlMap(map);
}
};
}
@Bean
public SimpleHandlerAdapter handlerAdapter() {
return new SimpleHandlerAdapter();
}
}
}

View File

@@ -0,0 +1,150 @@
/*
* 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.web.reactive.result.condition;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
/**
* Unit tests for {@link CompositeRequestCondition}.
*
* @author Rossen Stoyanchev
*/
public class CompositeRequestConditionTests {
private ServerHttpRequest request;
private ParamsRequestCondition param1;
private ParamsRequestCondition param2;
private ParamsRequestCondition param3;
private HeadersRequestCondition header1;
private HeadersRequestCondition header2;
private HeadersRequestCondition header3;
@Before
public void setup() throws Exception {
this.request = MockServerHttpRequest.get("/").build();
this.param1 = new ParamsRequestCondition("param1");
this.param2 = new ParamsRequestCondition("param2");
this.param3 = this.param1.combine(this.param2);
this.header1 = new HeadersRequestCondition("header1");
this.header2 = new HeadersRequestCondition("header2");
this.header3 = this.header1.combine(this.header2);
}
@Test
public void combine() {
CompositeRequestCondition cond1 = new CompositeRequestCondition(this.param1, this.header1);
CompositeRequestCondition cond2 = new CompositeRequestCondition(this.param2, this.header2);
CompositeRequestCondition cond3 = new CompositeRequestCondition(this.param3, this.header3);
assertEquals(cond3, cond1.combine(cond2));
}
@Test
public void combineEmpty() {
CompositeRequestCondition empty = new CompositeRequestCondition();
CompositeRequestCondition notEmpty = new CompositeRequestCondition(this.param1);
assertSame(empty, empty.combine(empty));
assertSame(notEmpty, notEmpty.combine(empty));
assertSame(notEmpty, empty.combine(notEmpty));
}
@Test(expected = IllegalArgumentException.class)
public void combineDifferentLength() {
CompositeRequestCondition cond1 = new CompositeRequestCondition(this.param1);
CompositeRequestCondition cond2 = new CompositeRequestCondition(this.param1, this.header1);
cond1.combine(cond2);
}
@Test
public void match() {
this.request = MockServerHttpRequest.get("/path?param1=paramValue1").build();
RequestCondition<?> condition1 = new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.POST);
RequestCondition<?> condition2 = new RequestMethodsRequestCondition(RequestMethod.GET);
CompositeRequestCondition composite1 = new CompositeRequestCondition(this.param1, condition1);
CompositeRequestCondition composite2 = new CompositeRequestCondition(this.param1, condition2);
assertEquals(composite2, composite1.getMatchingCondition(createExchange()));
}
@Test
public void noMatch() {
CompositeRequestCondition cond = new CompositeRequestCondition(this.param1);
assertNull(cond.getMatchingCondition(createExchange()));
}
@Test
public void matchEmpty() {
CompositeRequestCondition empty = new CompositeRequestCondition();
assertSame(empty, empty.getMatchingCondition(createExchange()));
}
@Test
public void compare() {
CompositeRequestCondition cond1 = new CompositeRequestCondition(this.param1);
CompositeRequestCondition cond3 = new CompositeRequestCondition(this.param3);
ServerWebExchange exchange = createExchange();
assertEquals(1, cond1.compareTo(cond3, exchange));
assertEquals(-1, cond3.compareTo(cond1, exchange));
}
@Test
public void compareEmpty() {
CompositeRequestCondition empty = new CompositeRequestCondition();
CompositeRequestCondition notEmpty = new CompositeRequestCondition(this.param1);
ServerWebExchange exchange = createExchange();
assertEquals(0, empty.compareTo(empty, exchange));
assertEquals(-1, notEmpty.compareTo(empty, exchange));
assertEquals(1, empty.compareTo(notEmpty, exchange));
}
@Test(expected = IllegalArgumentException.class)
public void compareDifferentLength() {
CompositeRequestCondition cond1 = new CompositeRequestCondition(this.param1);
CompositeRequestCondition cond2 = new CompositeRequestCondition(this.param1, this.header1);
cond1.compareTo(cond2, createExchange());
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright 2002-2012 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.result.condition;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.result.condition.ConsumesRequestCondition.ConsumeMediaTypeExpression;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Arjen Poutsma
*/
public class ConsumesRequestConditionTests {
@Test
public void consumesMatch() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/plain");
assertNotNull(condition.getMatchingCondition(exchange));
}
@Test
public void negatedConsumesMatch() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ConsumesRequestCondition condition = new ConsumesRequestCondition("!text/plain");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void getConsumableMediaTypesNegatedExpression() throws Exception {
ConsumesRequestCondition condition = new ConsumesRequestCondition("!application/xml");
assertEquals(Collections.emptySet(), condition.getConsumableMediaTypes());
}
@Test
public void consumesWildcardMatch() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/*");
assertNotNull(condition.getMatchingCondition(exchange));
}
@Test
public void consumesMultipleMatch() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/plain", "application/xml");
assertNotNull(condition.getMatchingCondition(exchange));
}
@Test
public void consumesSingleNoMatch() throws Exception {
ServerWebExchange exchange = createExchange("application/xml");
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/plain");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void consumesParseError() throws Exception {
ServerWebExchange exchange = createExchange("01");
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/plain");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void consumesParseErrorWithNegation() throws Exception {
ServerWebExchange exchange = createExchange("01");
ConsumesRequestCondition condition = new ConsumesRequestCondition("!text/plain");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void compareToSingle() throws Exception {
ServerWebExchange exchange = createExchange();
ConsumesRequestCondition condition1 = new ConsumesRequestCondition("text/plain");
ConsumesRequestCondition condition2 = new ConsumesRequestCondition("text/*");
int result = condition1.compareTo(condition2, exchange);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, exchange);
assertTrue("Invalid comparison result: " + result, result > 0);
}
@Test
public void compareToMultiple() throws Exception {
ServerWebExchange exchange = createExchange();
ConsumesRequestCondition condition1 = new ConsumesRequestCondition("*/*", "text/plain");
ConsumesRequestCondition condition2 = new ConsumesRequestCondition("text/*", "text/plain;q=0.7");
int result = condition1.compareTo(condition2, exchange);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, exchange);
assertTrue("Invalid comparison result: " + result, result > 0);
}
@Test
public void combine() throws Exception {
ConsumesRequestCondition condition1 = new ConsumesRequestCondition("text/plain");
ConsumesRequestCondition condition2 = new ConsumesRequestCondition("application/xml");
ConsumesRequestCondition result = condition1.combine(condition2);
assertEquals(condition2, result);
}
@Test
public void combineWithDefault() throws Exception {
ConsumesRequestCondition condition1 = new ConsumesRequestCondition("text/plain");
ConsumesRequestCondition condition2 = new ConsumesRequestCondition();
ConsumesRequestCondition result = condition1.combine(condition2);
assertEquals(condition1, result);
}
@Test
public void parseConsumesAndHeaders() throws Exception {
String[] consumes = new String[] {"text/plain"};
String[] headers = new String[]{"foo=bar", "content-type=application/xml,application/pdf"};
ConsumesRequestCondition condition = new ConsumesRequestCondition(consumes, headers);
assertConditions(condition, "text/plain", "application/xml", "application/pdf");
}
@Test
public void getMatchingCondition() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ConsumesRequestCondition condition = new ConsumesRequestCondition("text/plain", "application/xml");
ConsumesRequestCondition result = condition.getMatchingCondition(exchange);
assertConditions(result, "text/plain");
condition = new ConsumesRequestCondition("application/xml");
result = condition.getMatchingCondition(exchange);
assertNull(result);
}
private void assertConditions(ConsumesRequestCondition condition, String... expected) {
Collection<ConsumeMediaTypeExpression> expressions = condition.getContent();
assertEquals("Invalid amount of conditions", expressions.size(), expected.length);
for (String s : expected) {
boolean found = false;
for (ConsumeMediaTypeExpression expr : expressions) {
String conditionMediaType = expr.getMediaType().toString();
if (conditionMediaType.equals(s)) {
found = true;
break;
}
}
if (!found) {
fail("Condition [" + s + "] not found");
}
}
}
private ServerWebExchange createExchange() throws URISyntaxException {
return createExchange(null);
}
private ServerWebExchange createExchange(String contentType) {
MockServerHttpRequest request = (contentType != null ?
MockServerHttpRequest.post("/").header(HttpHeaders.CONTENT_TYPE, contentType).build() :
MockServerHttpRequest.get("/").build());
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,169 @@
/*
* Copyright 2002-2012 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.result.condition;
import java.net.URISyntaxException;
import java.util.Collection;
import org.junit.Test;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link HeadersRequestCondition}.
*
* @author Rossen Stoyanchev
*/
public class HeadersRequestConditionTests {
@Test
public void headerEquals() {
assertEquals(new HeadersRequestCondition("foo"), new HeadersRequestCondition("foo"));
assertEquals(new HeadersRequestCondition("foo"), new HeadersRequestCondition("FOO"));
assertFalse(new HeadersRequestCondition("foo").equals(new HeadersRequestCondition("bar")));
assertEquals(new HeadersRequestCondition("foo=bar"), new HeadersRequestCondition("foo=bar"));
assertEquals(new HeadersRequestCondition("foo=bar"), new HeadersRequestCondition("FOO=bar"));
}
@Test
public void headerPresent() throws Exception {
ServerWebExchange exchange = createExchange("Accept", "");
HeadersRequestCondition condition = new HeadersRequestCondition("accept");
assertNotNull(condition.getMatchingCondition(exchange));
}
@Test
public void headerPresentNoMatch() throws Exception {
ServerWebExchange exchange = createExchange("bar", "");
HeadersRequestCondition condition = new HeadersRequestCondition("foo");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void headerNotPresent() throws Exception {
ServerWebExchange exchange = createExchange();
HeadersRequestCondition condition = new HeadersRequestCondition("!accept");
assertNotNull(condition.getMatchingCondition(exchange));
}
@Test
public void headerValueMatch() throws Exception {
ServerWebExchange exchange = createExchange("foo", "bar");
HeadersRequestCondition condition = new HeadersRequestCondition("foo=bar");
assertNotNull(condition.getMatchingCondition(exchange));
}
@Test
public void headerValueNoMatch() throws Exception {
ServerWebExchange exchange = createExchange("foo", "bazz");
HeadersRequestCondition condition = new HeadersRequestCondition("foo=bar");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void headerCaseSensitiveValueMatch() throws Exception {
ServerWebExchange exchange = createExchange("foo", "bar");
HeadersRequestCondition condition = new HeadersRequestCondition("foo=Bar");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void headerValueMatchNegated() throws Exception {
ServerWebExchange exchange = createExchange("foo", "baz");
HeadersRequestCondition condition = new HeadersRequestCondition("foo!=bar");
assertNotNull(condition.getMatchingCondition(exchange));
}
@Test
public void headerValueNoMatchNegated() throws Exception {
ServerWebExchange exchange = createExchange("foo", "bar");
HeadersRequestCondition condition = new HeadersRequestCondition("foo!=bar");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void compareTo() throws Exception {
ServerWebExchange exchange = createExchange();
HeadersRequestCondition condition1 = new HeadersRequestCondition("foo", "bar", "baz");
HeadersRequestCondition condition2 = new HeadersRequestCondition("foo", "bar");
int result = condition1.compareTo(condition2, exchange);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, exchange);
assertTrue("Invalid comparison result: " + result, result > 0);
}
@Test
public void combine() {
HeadersRequestCondition condition1 = new HeadersRequestCondition("foo=bar");
HeadersRequestCondition condition2 = new HeadersRequestCondition("foo=baz");
HeadersRequestCondition result = condition1.combine(condition2);
Collection<?> conditions = result.getContent();
assertEquals(2, conditions.size());
}
@Test
public void getMatchingCondition() throws Exception {
ServerWebExchange exchange = createExchange("foo", "bar");
HeadersRequestCondition condition = new HeadersRequestCondition("foo");
HeadersRequestCondition result = condition.getMatchingCondition(exchange);
assertEquals(condition, result);
condition = new HeadersRequestCondition("bar");
result = condition.getMatchingCondition(exchange);
assertNull(result);
}
private ServerWebExchange createExchange() throws URISyntaxException {
return createExchange(null, null);
}
private ServerWebExchange createExchange(String headerName, String headerValue) {
ServerHttpRequest request = headerName != null ?
MockServerHttpRequest.get("/").header(headerName, headerValue).build() :
MockServerHttpRequest.get("/").build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2002-2012 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.result.condition;
import java.net.URISyntaxException;
import java.util.Collection;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link ParamsRequestCondition}.
* @author Rossen Stoyanchev
*/
public class ParamsRequestConditionTests {
@Test
public void paramEquals() {
assertEquals(new ParamsRequestCondition("foo"), new ParamsRequestCondition("foo"));
assertFalse(new ParamsRequestCondition("foo").equals(new ParamsRequestCondition("bar")));
assertFalse(new ParamsRequestCondition("foo").equals(new ParamsRequestCondition("FOO")));
assertEquals(new ParamsRequestCondition("foo=bar"), new ParamsRequestCondition("foo=bar"));
assertFalse(new ParamsRequestCondition("foo=bar").equals(new ParamsRequestCondition("FOO=bar")));
}
@Test
public void paramPresent() throws Exception {
ParamsRequestCondition condition = new ParamsRequestCondition("foo");
assertNotNull(condition.getMatchingCondition(exchangeWithQuery("foo=")));
assertNotNull(condition.getMatchingCondition(exchangeWithFormData("foo=")));
}
@Test
public void paramPresentNoMatch() throws Exception {
ParamsRequestCondition condition = new ParamsRequestCondition("foo");
assertNull(condition.getMatchingCondition(exchangeWithQuery("bar=")));
assertNull(condition.getMatchingCondition(exchangeWithFormData("bar=")));
}
@Test
public void paramNotPresent() throws Exception {
ServerWebExchange exchange = exchange();
assertNotNull(new ParamsRequestCondition("!foo").getMatchingCondition(exchange));
}
@Test
public void paramValueMatch() throws Exception {
ParamsRequestCondition condition = new ParamsRequestCondition("foo=bar");
assertNotNull(condition.getMatchingCondition(exchangeWithQuery("foo=bar")));
assertNotNull(condition.getMatchingCondition(exchangeWithFormData("foo=bar")));
}
@Test
public void paramValueNoMatch() throws Exception {
ParamsRequestCondition condition = new ParamsRequestCondition("foo=bar");
assertNull(condition.getMatchingCondition(exchangeWithQuery("foo=bazz")));
assertNull(condition.getMatchingCondition(exchangeWithFormData("foo=bazz")));
}
@Test
public void compareTo() throws Exception {
ServerHttpRequest request = MockServerHttpRequest.get("/").build();
ServerWebExchange exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
ParamsRequestCondition condition1 = new ParamsRequestCondition("foo", "bar", "baz");
ParamsRequestCondition condition2 = new ParamsRequestCondition("foo", "bar");
int result = condition1.compareTo(condition2, exchange);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, exchange);
assertTrue("Invalid comparison result: " + result, result > 0);
}
@Test
public void combine() {
ParamsRequestCondition condition1 = new ParamsRequestCondition("foo=bar");
ParamsRequestCondition condition2 = new ParamsRequestCondition("foo=baz");
ParamsRequestCondition result = condition1.combine(condition2);
Collection<?> conditions = result.getContent();
assertEquals(2, conditions.size());
}
private ServerWebExchange exchangeWithQuery(String query) throws URISyntaxException {
ServerHttpRequest request = MockServerHttpRequest.get("/path?" + query).build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
private ServerWebExchange exchangeWithFormData(String formData) throws URISyntaxException {
MockServerHttpRequest request = MockServerHttpRequest.post("/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(formData);
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
private ServerWebExchange exchange() {
MockServerHttpRequest request = MockServerHttpRequest.get("/").build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,229 @@
/*
* 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.web.reactive.result.condition;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Set;
import org.junit.Test;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* Unit tests for {@link PatternsRequestCondition}.
*
* @author Rossen Stoyanchev
*/
public class PatternsRequestConditionTests {
@Test
public void prependSlash() {
PatternsRequestCondition c = new PatternsRequestCondition("foo");
assertEquals("/foo", c.getPatterns().iterator().next());
}
@Test
public void prependNonEmptyPatternsOnly() {
PatternsRequestCondition c = new PatternsRequestCondition("");
assertEquals("Do not prepend empty patterns (SPR-8255)", "", c.getPatterns().iterator().next());
}
@Test
public void combineEmptySets() {
PatternsRequestCondition c1 = new PatternsRequestCondition();
PatternsRequestCondition c2 = new PatternsRequestCondition();
assertEquals(new PatternsRequestCondition(""), c1.combine(c2));
}
@Test
public void combineOnePatternWithEmptySet() {
PatternsRequestCondition c1 = new PatternsRequestCondition("/type1", "/type2");
PatternsRequestCondition c2 = new PatternsRequestCondition();
assertEquals(new PatternsRequestCondition("/type1", "/type2"), c1.combine(c2));
c1 = new PatternsRequestCondition();
c2 = new PatternsRequestCondition("/method1", "/method2");
assertEquals(new PatternsRequestCondition("/method1", "/method2"), c1.combine(c2));
}
@Test
public void combineMultiplePatterns() {
PatternsRequestCondition c1 = new PatternsRequestCondition("/t1", "/t2");
PatternsRequestCondition c2 = new PatternsRequestCondition("/m1", "/m2");
assertEquals(new PatternsRequestCondition("/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2"), c1.combine(c2));
}
@Test
public void matchDirectPath() throws Exception {
PatternsRequestCondition condition = new PatternsRequestCondition("/foo");
PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo"));
assertNotNull(match);
}
@Test
public void matchPattern() throws Exception {
PatternsRequestCondition condition = new PatternsRequestCondition("/foo/*");
PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo/bar"));
assertNotNull(match);
}
@Test
public void matchSortPatterns() throws Exception {
PatternsRequestCondition condition = new PatternsRequestCondition("/**", "/foo/bar", "/foo/*");
PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo/bar"));
PatternsRequestCondition expected = new PatternsRequestCondition("/foo/bar", "/foo/*", "/**");
assertEquals(expected, match);
}
@Test
public void matchSuffixPattern() throws Exception {
ServerWebExchange exchange = createExchange("/foo.html");
PatternsRequestCondition condition = new PatternsRequestCondition("/{foo}");
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
assertNotNull(match);
assertEquals("/{foo}.*", match.getPatterns().iterator().next());
condition = new PatternsRequestCondition(new String[] {"/{foo}"}, null, null, false, false, null);
match = condition.getMatchingCondition(exchange);
assertNotNull(match);
assertEquals("/{foo}", match.getPatterns().iterator().next());
}
// SPR-8410
@Test
public void matchSuffixPatternUsingFileExtensions() throws Exception {
String[] patterns = new String[] {"/jobs/{jobName}"};
Set<String> extensions = Collections.singleton("json");
PatternsRequestCondition condition = new PatternsRequestCondition(patterns, null, null, true, false, extensions);
ServerWebExchange exchange = createExchange("/jobs/my.job");
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
assertNotNull(match);
assertEquals("/jobs/{jobName}", match.getPatterns().iterator().next());
exchange = createExchange("/jobs/my.job.json");
match = condition.getMatchingCondition(exchange);
assertNotNull(match);
assertEquals("/jobs/{jobName}.json", match.getPatterns().iterator().next());
}
@Test
public void matchSuffixPatternUsingFileExtensions2() throws Exception {
PatternsRequestCondition condition1 = new PatternsRequestCondition(
new String[] {"/prefix"}, null, null, true, false, Collections.singleton("json"));
PatternsRequestCondition condition2 = new PatternsRequestCondition(
new String[] {"/suffix"}, null, null, true, false, null);
PatternsRequestCondition combined = condition1.combine(condition2);
ServerWebExchange exchange = createExchange("/prefix/suffix.json");
PatternsRequestCondition match = combined.getMatchingCondition(exchange);
assertNotNull(match);
}
@Test
public void matchTrailingSlash() throws Exception {
ServerWebExchange exchange = createExchange("/foo/");
PatternsRequestCondition condition = new PatternsRequestCondition("/foo");
PatternsRequestCondition match = condition.getMatchingCondition(exchange);
assertNotNull(match);
assertEquals("Should match by default", "/foo/", match.getPatterns().iterator().next());
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, true, null);
match = condition.getMatchingCondition(exchange);
assertNotNull(match);
assertEquals("Trailing slash should be insensitive to useSuffixPatternMatch settings (SPR-6164, SPR-5636)",
"/foo/", match.getPatterns().iterator().next());
condition = new PatternsRequestCondition(new String[] {"/foo"}, null, null, false, false, null);
match = condition.getMatchingCondition(exchange);
assertNull(match);
}
@Test
public void matchPatternContainsExtension() throws Exception {
PatternsRequestCondition condition = new PatternsRequestCondition("/foo.jpg");
PatternsRequestCondition match = condition.getMatchingCondition(createExchange("/foo.html"));
assertNull(match);
}
@Test
public void compareEqualPatterns() throws Exception {
PatternsRequestCondition c1 = new PatternsRequestCondition("/foo*");
PatternsRequestCondition c2 = new PatternsRequestCondition("/foo*");
assertEquals(0, c1.compareTo(c2, createExchange("/foo")));
}
@Test
public void comparePatternSpecificity() throws Exception {
PatternsRequestCondition c1 = new PatternsRequestCondition("/fo*");
PatternsRequestCondition c2 = new PatternsRequestCondition("/foo");
assertEquals(1, c1.compareTo(c2, createExchange("/foo")));
}
@Test
public void compareNumberOfMatchingPatterns() throws Exception {
ServerWebExchange exchange = createExchange("/foo.html");
PatternsRequestCondition c1 = new PatternsRequestCondition("/foo", "*.jpeg");
PatternsRequestCondition c2 = new PatternsRequestCondition("/foo", "*.html");
PatternsRequestCondition match1 = c1.getMatchingCondition(exchange);
PatternsRequestCondition match2 = c2.getMatchingCondition(exchange);
assertNotNull(match1);
assertEquals(1, match1.compareTo(match2, exchange));
}
private ServerWebExchange createExchange(String path) throws URISyntaxException {
ServerHttpRequest request = MockServerHttpRequest.get(path).build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,321 @@
/*
* 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.web.reactive.result.condition;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import org.junit.Test;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Unit tests for {@link ProducesRequestCondition}.
*
* @author Rossen Stoyanchev
*/
public class ProducesRequestConditionTests {
@Test
public void match() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain");
assertNotNull(condition.getMatchingCondition(exchange));
}
@Test
public void matchNegated() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ProducesRequestCondition condition = new ProducesRequestCondition("!text/plain");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void getProducibleMediaTypes() throws Exception {
ProducesRequestCondition condition = new ProducesRequestCondition("!application/xml");
assertEquals(Collections.emptySet(), condition.getProducibleMediaTypes());
}
@Test
public void matchWildcard() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ProducesRequestCondition condition = new ProducesRequestCondition("text/*");
assertNotNull(condition.getMatchingCondition(exchange));
}
@Test
public void matchMultiple() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain", "application/xml");
assertNotNull(condition.getMatchingCondition(exchange));
}
@Test
public void matchSingle() throws Exception {
ServerWebExchange exchange = createExchange("application/xml");
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void matchParseError() throws Exception {
ServerWebExchange exchange = createExchange("bogus");
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void matchParseErrorWithNegation() throws Exception {
ServerWebExchange exchange = createExchange("bogus");
ProducesRequestCondition condition = new ProducesRequestCondition("!text/plain");
assertNull(condition.getMatchingCondition(exchange));
}
@Test
public void compareTo() throws Exception {
ProducesRequestCondition html = new ProducesRequestCondition("text/html");
ProducesRequestCondition xml = new ProducesRequestCondition("application/xml");
ProducesRequestCondition none = new ProducesRequestCondition();
ServerWebExchange exchange = createExchange("application/xml, text/html");
assertTrue(html.compareTo(xml, exchange) > 0);
assertTrue(xml.compareTo(html, exchange) < 0);
assertTrue(xml.compareTo(none, exchange) < 0);
assertTrue(none.compareTo(xml, exchange) > 0);
assertTrue(html.compareTo(none, exchange) < 0);
assertTrue(none.compareTo(html, exchange) > 0);
exchange = createExchange("application/xml, text/*");
assertTrue(html.compareTo(xml, exchange) > 0);
assertTrue(xml.compareTo(html, exchange) < 0);
exchange = createExchange("application/pdf");
assertTrue(html.compareTo(xml, exchange) == 0);
assertTrue(xml.compareTo(html, exchange) == 0);
// See SPR-7000
exchange = createExchange("text/html;q=0.9,application/xml");
assertTrue(html.compareTo(xml, exchange) > 0);
assertTrue(xml.compareTo(html, exchange) < 0);
}
@Test
public void compareToWithSingleExpression() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*");
int result = condition1.compareTo(condition2, exchange);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, exchange);
assertTrue("Invalid comparison result: " + result, result > 0);
}
@Test
public void compareToMultipleExpressions() throws Exception {
ProducesRequestCondition condition1 = new ProducesRequestCondition("*/*", "text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/*", "text/plain;q=0.7");
ServerWebExchange exchange = createExchange("text/plain");
int result = condition1.compareTo(condition2, exchange);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, exchange);
assertTrue("Invalid comparison result: " + result, result > 0);
}
@Test
public void compareToMultipleExpressionsAndMultipeAcceptHeaderValues() throws Exception {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/*", "text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/*", "application/xml");
ServerWebExchange exchange = createExchange("text/plain", "application/xml");
int result = condition1.compareTo(condition2, exchange);
assertTrue("Invalid comparison result: " + result, result < 0);
result = condition2.compareTo(condition1, exchange);
assertTrue("Invalid comparison result: " + result, result > 0);
exchange = createExchange("application/xml", "text/plain");
result = condition1.compareTo(condition2, exchange);
assertTrue("Invalid comparison result: " + result, result > 0);
result = condition2.compareTo(condition1, exchange);
assertTrue("Invalid comparison result: " + result, result < 0);
}
// SPR-8536
@Test
public void compareToMediaTypeAll() throws Exception {
ServerWebExchange exchange = createExchange();
ProducesRequestCondition condition1 = new ProducesRequestCondition();
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/json");
assertTrue("Should have picked '*/*' condition as an exact match",
condition1.compareTo(condition2, exchange) < 0);
assertTrue("Should have picked '*/*' condition as an exact match",
condition2.compareTo(condition1, exchange) > 0);
condition1 = new ProducesRequestCondition("*/*");
condition2 = new ProducesRequestCondition("application/json");
assertTrue(condition1.compareTo(condition2, exchange) < 0);
assertTrue(condition2.compareTo(condition1, exchange) > 0);
exchange = createExchange("*/*");
condition1 = new ProducesRequestCondition();
condition2 = new ProducesRequestCondition("application/json");
assertTrue(condition1.compareTo(condition2, exchange) < 0);
assertTrue(condition2.compareTo(condition1, exchange) > 0);
condition1 = new ProducesRequestCondition("*/*");
condition2 = new ProducesRequestCondition("application/json");
assertTrue(condition1.compareTo(condition2, exchange) < 0);
assertTrue(condition2.compareTo(condition1, exchange) > 0);
}
// SPR-9021
@Test
public void compareToMediaTypeAllWithParameter() throws Exception {
ServerWebExchange exchange = createExchange("*/*;q=0.9");
ProducesRequestCondition condition1 = new ProducesRequestCondition();
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/json");
assertTrue(condition1.compareTo(condition2, exchange) < 0);
assertTrue(condition2.compareTo(condition1, exchange) > 0);
}
@Test
public void compareToEqualMatch() throws Exception {
ServerWebExchange exchange = createExchange("text/*");
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("text/xhtml");
int result = condition1.compareTo(condition2, exchange);
assertTrue("Should have used MediaType.equals(Object) to break the match", result < 0);
result = condition2.compareTo(condition1, exchange);
assertTrue("Should have used MediaType.equals(Object) to break the match", result > 0);
}
@Test
public void combine() throws Exception {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("application/xml");
ProducesRequestCondition result = condition1.combine(condition2);
assertEquals(condition2, result);
}
@Test
public void combineWithDefault() throws Exception {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition();
ProducesRequestCondition result = condition1.combine(condition2);
assertEquals(condition1, result);
}
@Test
public void instantiateWithProducesAndHeaderConditions() throws Exception {
String[] produces = new String[] {"text/plain"};
String[] headers = new String[]{"foo=bar", "accept=application/xml,application/pdf"};
ProducesRequestCondition condition = new ProducesRequestCondition(produces, headers);
assertConditions(condition, "text/plain", "application/xml", "application/pdf");
}
@Test
public void getMatchingCondition() throws Exception {
ServerWebExchange exchange = createExchange("text/plain");
ProducesRequestCondition condition = new ProducesRequestCondition("text/plain", "application/xml");
ProducesRequestCondition result = condition.getMatchingCondition(exchange);
assertConditions(result, "text/plain");
condition = new ProducesRequestCondition("application/xml");
result = condition.getMatchingCondition(exchange);
assertNull(result);
}
private void assertConditions(ProducesRequestCondition condition, String... expected) {
Collection<ProducesRequestCondition.ProduceMediaTypeExpression> expressions = condition.getContent();
assertEquals("Invalid number of conditions", expressions.size(), expected.length);
for (String s : expected) {
boolean found = false;
for (ProducesRequestCondition.ProduceMediaTypeExpression expr : expressions) {
String conditionMediaType = expr.getMediaType().toString();
if (conditionMediaType.equals(s)) {
found = true;
break;
}
}
if (!found) {
fail("Condition [" + s + "] not found");
}
}
}
private ServerWebExchange createExchange(String... accept) throws URISyntaxException {
ServerHttpRequest request = (accept != null ?
MockServerHttpRequest.get("/").header("Accept", accept).build() :
MockServerHttpRequest.get("/").build());
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.web.reactive.result.condition;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
/**
* Unit tests for {@link RequestConditionHolder}.
*
* @author Rossen Stoyanchev
*/
public class RequestConditionHolderTests {
private ServerWebExchange exchange;
@Before
public void setUp() throws Exception {
ServerHttpRequest request = MockServerHttpRequest.get("/").build();
MockServerHttpResponse response = new MockServerHttpResponse();
this.exchange = new DefaultServerWebExchange(request, response);
}
@Test
public void combine() {
RequestConditionHolder params1 = new RequestConditionHolder(new ParamsRequestCondition("name1"));
RequestConditionHolder params2 = new RequestConditionHolder(new ParamsRequestCondition("name2"));
RequestConditionHolder expected = new RequestConditionHolder(new ParamsRequestCondition("name1", "name2"));
assertEquals(expected, params1.combine(params2));
}
@Test
public void combineEmpty() {
RequestConditionHolder empty = new RequestConditionHolder(null);
RequestConditionHolder notEmpty = new RequestConditionHolder(new ParamsRequestCondition("name"));
assertSame(empty, empty.combine(empty));
assertSame(notEmpty, notEmpty.combine(empty));
assertSame(notEmpty, empty.combine(notEmpty));
}
@Test(expected = ClassCastException.class)
public void combineIncompatible() {
RequestConditionHolder params = new RequestConditionHolder(new ParamsRequestCondition("name"));
RequestConditionHolder headers = new RequestConditionHolder(new HeadersRequestCondition("name"));
params.combine(headers);
}
@Test
public void match() {
RequestMethodsRequestCondition rm = new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.POST);
RequestConditionHolder custom = new RequestConditionHolder(rm);
RequestMethodsRequestCondition expected = new RequestMethodsRequestCondition(RequestMethod.GET);
RequestConditionHolder holder = custom.getMatchingCondition(this.exchange);
assertNotNull(holder);
assertEquals(expected, holder.getCondition());
}
@Test
public void noMatch() {
RequestMethodsRequestCondition rm = new RequestMethodsRequestCondition(RequestMethod.POST);
RequestConditionHolder custom = new RequestConditionHolder(rm);
assertNull(custom.getMatchingCondition(this.exchange));
}
@Test
public void matchEmpty() {
RequestConditionHolder empty = new RequestConditionHolder(null);
assertSame(empty, empty.getMatchingCondition(this.exchange));
}
@Test
public void compare() {
RequestConditionHolder params11 = new RequestConditionHolder(new ParamsRequestCondition("1"));
RequestConditionHolder params12 = new RequestConditionHolder(new ParamsRequestCondition("1", "2"));
assertEquals(1, params11.compareTo(params12, this.exchange));
assertEquals(-1, params12.compareTo(params11, this.exchange));
}
@Test
public void compareEmpty() {
RequestConditionHolder empty = new RequestConditionHolder(null);
RequestConditionHolder empty2 = new RequestConditionHolder(null);
RequestConditionHolder notEmpty = new RequestConditionHolder(new ParamsRequestCondition("name"));
assertEquals(0, empty.compareTo(empty2, this.exchange));
assertEquals(-1, notEmpty.compareTo(empty, this.exchange));
assertEquals(1, empty.compareTo(notEmpty, this.exchange));
}
@Test(expected = ClassCastException.class)
public void compareIncompatible() {
RequestConditionHolder params = new RequestConditionHolder(new ParamsRequestCondition("name"));
RequestConditionHolder headers = new RequestConditionHolder(new HeadersRequestCondition("name"));
params.compareTo(headers, this.exchange);
}
}

View File

@@ -0,0 +1,353 @@
/*
* 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.web.reactive.result.condition;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* Unit tests for {@link RequestMappingInfo}.
*
* @author Rossen Stoyanchev
*/
public class RequestMappingInfoTests {
private ServerHttpRequest request;
// TODO: CORS pre-flight (see @Ignored)
@Before
public void setUp() throws Exception {
this.request = MockServerHttpRequest.get("/foo").build();
}
@Test
public void createEmpty() {
RequestMappingInfo info = new RequestMappingInfo(null, null, null, null, null, null, null);
assertEquals(0, info.getPatternsCondition().getPatterns().size());
assertEquals(0, info.getMethodsCondition().getMethods().size());
assertEquals(true, info.getConsumesCondition().isEmpty());
assertEquals(true, info.getProducesCondition().isEmpty());
assertNotNull(info.getParamsCondition());
assertNotNull(info.getHeadersCondition());
assertNull(info.getCustomCondition());
}
@Test
public void matchPatternsCondition() {
RequestMappingInfo info = new RequestMappingInfo(
new PatternsRequestCondition("/foo*", "/bar"), null, null, null, null, null, null);
RequestMappingInfo expected = new RequestMappingInfo(
new PatternsRequestCondition("/foo*"), null, null, null, null, null, null);
assertEquals(expected, info.getMatchingCondition(createExchange()));
info = new RequestMappingInfo(
new PatternsRequestCondition("/**", "/foo*", "/foo"), null, null, null, null, null, null);
expected = new RequestMappingInfo(
new PatternsRequestCondition("/foo", "/foo*", "/**"), null, null, null, null, null, null);
assertEquals(expected, info.getMatchingCondition(createExchange()));
}
@Test
public void matchParamsCondition() {
this.request = MockServerHttpRequest.get("/foo?foo=bar").build();
RequestMappingInfo info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null,
new ParamsRequestCondition("foo=bar"), null, null, null, null);
RequestMappingInfo match = info.getMatchingCondition(createExchange());
assertNotNull(match);
info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null,
new ParamsRequestCondition("foo!=bar"), null, null, null, null);
match = info.getMatchingCondition(createExchange());
assertNull(match);
}
@Test
public void matchHeadersCondition() {
this.request = MockServerHttpRequest.get("/foo").header("foo", "bar").build();
RequestMappingInfo info =
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null,
new HeadersRequestCondition("foo=bar"), null, null, null);
RequestMappingInfo match = info.getMatchingCondition(createExchange());
assertNotNull(match);
info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null,
new HeadersRequestCondition("foo!=bar"), null, null, null);
match = info.getMatchingCondition(createExchange());
assertNull(match);
}
@Test
public void matchConsumesCondition() {
this.request = MockServerHttpRequest.post("/foo").contentType(MediaType.TEXT_PLAIN).build();
RequestMappingInfo info =
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null,
new ConsumesRequestCondition("text/plain"), null, null);
RequestMappingInfo match = info.getMatchingCondition(createExchange());
assertNotNull(match);
info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null,
new ConsumesRequestCondition("application/xml"), null, null);
match = info.getMatchingCondition(createExchange());
assertNull(match);
}
@Test
public void matchProducesCondition() {
this.request = MockServerHttpRequest.get("/foo").accept(MediaType.TEXT_PLAIN).build();
RequestMappingInfo info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null, null,
new ProducesRequestCondition("text/plain"), null);
RequestMappingInfo match = info.getMatchingCondition(createExchange());
assertNotNull(match);
info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null, null,
new ProducesRequestCondition("application/xml"), null);
match = info.getMatchingCondition(createExchange());
assertNull(match);
}
@Test
public void matchCustomCondition() {
this.request = MockServerHttpRequest.get("/foo?foo=bar").build();
RequestMappingInfo info =
new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null, null, null, null, null,
new ParamsRequestCondition("foo=bar"));
RequestMappingInfo match = info.getMatchingCondition(createExchange());
assertNotNull(match);
info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), null,
new ParamsRequestCondition("foo!=bar"), null, null, null,
new ParamsRequestCondition("foo!=bar"));
match = info.getMatchingCondition(createExchange());
assertNull(match);
}
@Test
public void compareTwoHttpMethodsOneParam() {
RequestMappingInfo none = new RequestMappingInfo(null, null, null, null, null, null, null);
RequestMappingInfo oneMethod =
new RequestMappingInfo(null,
new RequestMethodsRequestCondition(RequestMethod.GET), null, null, null, null, null);
RequestMappingInfo oneMethodOneParam =
new RequestMappingInfo(null,
new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition("foo"), null, null, null, null);
Comparator<RequestMappingInfo> comparator = (info, otherInfo) -> info.compareTo(otherInfo, createExchange());
List<RequestMappingInfo> list = asList(none, oneMethod, oneMethodOneParam);
Collections.shuffle(list);
Collections.sort(list, comparator);
assertEquals(oneMethodOneParam, list.get(0));
assertEquals(oneMethod, list.get(1));
assertEquals(none, list.get(2));
}
@Test
public void equals() {
RequestMappingInfo info1 = new RequestMappingInfo(
new PatternsRequestCondition("/foo"),
new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition("foo=bar"),
new HeadersRequestCondition("foo=bar"),
new ConsumesRequestCondition("text/plain"),
new ProducesRequestCondition("text/plain"),
new ParamsRequestCondition("customFoo=customBar"));
RequestMappingInfo info2 = new RequestMappingInfo(
new PatternsRequestCondition("/foo"),
new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition("foo=bar"),
new HeadersRequestCondition("foo=bar"),
new ConsumesRequestCondition("text/plain"),
new ProducesRequestCondition("text/plain"),
new ParamsRequestCondition("customFoo=customBar"));
assertEquals(info1, info2);
assertEquals(info1.hashCode(), info2.hashCode());
info2 = new RequestMappingInfo(
new PatternsRequestCondition("/foo", "/NOOOOOO"),
new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition("foo=bar"),
new HeadersRequestCondition("foo=bar"),
new ConsumesRequestCondition("text/plain"),
new ProducesRequestCondition("text/plain"),
new ParamsRequestCondition("customFoo=customBar"));
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
info2 = new RequestMappingInfo(
new PatternsRequestCondition("/foo"),
new RequestMethodsRequestCondition(RequestMethod.GET, RequestMethod.POST),
new ParamsRequestCondition("foo=bar"),
new HeadersRequestCondition("foo=bar"),
new ConsumesRequestCondition("text/plain"),
new ProducesRequestCondition("text/plain"),
new ParamsRequestCondition("customFoo=customBar"));
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
info2 = new RequestMappingInfo(
new PatternsRequestCondition("/foo"),
new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition("/NOOOOOO"),
new HeadersRequestCondition("foo=bar"),
new ConsumesRequestCondition("text/plain"),
new ProducesRequestCondition("text/plain"),
new ParamsRequestCondition("customFoo=customBar"));
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
info2 = new RequestMappingInfo(
new PatternsRequestCondition("/foo"),
new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition("foo=bar"),
new HeadersRequestCondition("/NOOOOOO"),
new ConsumesRequestCondition("text/plain"),
new ProducesRequestCondition("text/plain"),
new ParamsRequestCondition("customFoo=customBar"));
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
info2 = new RequestMappingInfo(
new PatternsRequestCondition("/foo"),
new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition("foo=bar"),
new HeadersRequestCondition("foo=bar"),
new ConsumesRequestCondition("text/NOOOOOO"),
new ProducesRequestCondition("text/plain"),
new ParamsRequestCondition("customFoo=customBar"));
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
info2 = new RequestMappingInfo(
new PatternsRequestCondition("/foo"),
new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition("foo=bar"),
new HeadersRequestCondition("foo=bar"),
new ConsumesRequestCondition("text/plain"),
new ProducesRequestCondition("text/NOOOOOO"),
new ParamsRequestCondition("customFoo=customBar"));
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
info2 = new RequestMappingInfo(
new PatternsRequestCondition("/foo"),
new RequestMethodsRequestCondition(RequestMethod.GET),
new ParamsRequestCondition("foo=bar"),
new HeadersRequestCondition("foo=bar"),
new ConsumesRequestCondition("text/plain"),
new ProducesRequestCondition("text/plain"),
new ParamsRequestCondition("customFoo=NOOOOOO"));
assertFalse(info1.equals(info2));
assertNotEquals(info1.hashCode(), info2.hashCode());
}
@Test
@Ignore
public void preFlightRequest() throws Exception {
ServerHttpRequest request = MockServerHttpRequest.options("/foo")
.header("Origin", "http://domain.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "POST")
.build();
WebSessionManager manager = new MockWebSessionManager();
MockServerHttpResponse response = new MockServerHttpResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(request, response, manager);
RequestMappingInfo info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), new RequestMethodsRequestCondition(RequestMethod.POST), null,
null, null, null, null);
RequestMappingInfo match = info.getMatchingCondition(exchange);
assertNotNull(match);
info = new RequestMappingInfo(
new PatternsRequestCondition("/foo"), new RequestMethodsRequestCondition(RequestMethod.OPTIONS), null,
null, null, null, null);
match = info.getMatchingCondition(exchange);
assertNull("Pre-flight should match the ACCESS_CONTROL_REQUEST_METHOD", match);
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,148 @@
/*
* 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.web.reactive.result.condition;
import java.net.URISyntaxException;
import java.util.Collections;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.HEAD;
import static org.springframework.web.bind.annotation.RequestMethod.OPTIONS;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;
/**
* Unit tests for {@link RequestMethodsRequestCondition}.
*
* @author Rossen Stoyanchev
*/
public class RequestMethodsRequestConditionTests {
// TODO: custom method, CORS pre-flight (see @Ignored)
@Test
public void getMatchingCondition() throws Exception {
testMatch(new RequestMethodsRequestCondition(GET), GET);
testMatch(new RequestMethodsRequestCondition(GET, POST), GET);
testNoMatch(new RequestMethodsRequestCondition(GET), POST);
}
@Test
public void getMatchingConditionWithHttpHead() throws Exception {
testMatch(new RequestMethodsRequestCondition(HEAD), HEAD);
testMatch(new RequestMethodsRequestCondition(GET), HEAD);
testNoMatch(new RequestMethodsRequestCondition(POST), HEAD);
}
@Test
public void getMatchingConditionWithEmptyConditions() throws Exception {
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
for (RequestMethod method : RequestMethod.values()) {
if (!OPTIONS.equals(method)) {
ServerWebExchange exchange = createExchange(method.name());
assertNotNull(condition.getMatchingCondition(exchange));
}
}
testNoMatch(condition, OPTIONS);
}
@Test
@Ignore
public void getMatchingConditionWithCustomMethod() throws Exception {
ServerWebExchange exchange = createExchange("PROPFIND");
assertNotNull(new RequestMethodsRequestCondition().getMatchingCondition(exchange));
assertNull(new RequestMethodsRequestCondition(GET, POST).getMatchingCondition(exchange));
}
@Test
@Ignore
public void getMatchingConditionWithCorsPreFlight() throws Exception {
ServerWebExchange exchange = createExchange("OPTIONS");
exchange.getRequest().getHeaders().add("Origin", "http://example.com");
exchange.getRequest().getHeaders().add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "PUT");
assertNotNull(new RequestMethodsRequestCondition().getMatchingCondition(exchange));
assertNotNull(new RequestMethodsRequestCondition(PUT).getMatchingCondition(exchange));
assertNull(new RequestMethodsRequestCondition(DELETE).getMatchingCondition(exchange));
}
@Test
public void compareTo() throws Exception {
RequestMethodsRequestCondition c1 = new RequestMethodsRequestCondition(GET, HEAD);
RequestMethodsRequestCondition c2 = new RequestMethodsRequestCondition(POST);
RequestMethodsRequestCondition c3 = new RequestMethodsRequestCondition();
ServerWebExchange exchange = createExchange("GET");
int result = c1.compareTo(c2, exchange);
assertTrue("Invalid comparison result: " + result, result < 0);
result = c2.compareTo(c1, exchange);
assertTrue("Invalid comparison result: " + result, result > 0);
result = c2.compareTo(c3, exchange);
assertTrue("Invalid comparison result: " + result, result < 0);
result = c1.compareTo(c1, exchange);
assertEquals("Invalid comparison result ", 0, result);
}
@Test
public void combine() {
RequestMethodsRequestCondition condition1 = new RequestMethodsRequestCondition(GET);
RequestMethodsRequestCondition condition2 = new RequestMethodsRequestCondition(POST);
RequestMethodsRequestCondition result = condition1.combine(condition2);
assertEquals(2, result.getContent().size());
}
private void testMatch(RequestMethodsRequestCondition condition, RequestMethod method) throws Exception {
ServerWebExchange exchange = createExchange(method.name());
RequestMethodsRequestCondition actual = condition.getMatchingCondition(exchange);
assertNotNull(actual);
assertEquals(Collections.singleton(method), actual.getContent());
}
private void testNoMatch(RequestMethodsRequestCondition condition, RequestMethod method) throws Exception {
ServerWebExchange exchange = createExchange(method.name());
assertNull(condition.getMatchingCondition(exchange));
}
private ServerWebExchange createExchange(String method) throws URISyntaxException {
ServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.valueOf(method), "/").build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
}

View File

@@ -0,0 +1,206 @@
/*
* 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.web.reactive.result.method;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* Unit tests for {@link AbstractHandlerMethodMapping}.
* @author Rossen Stoyanchev
*/
public class HandlerMethodMappingTests {
private AbstractHandlerMethodMapping<String> mapping;
private MyHandler handler;
private Method method1;
private Method method2;
@Before
public void setUp() throws Exception {
this.mapping = new MyHandlerMethodMapping();
this.handler = new MyHandler();
this.method1 = handler.getClass().getMethod("handlerMethod1");
this.method2 = handler.getClass().getMethod("handlerMethod2");
}
@Test(expected = IllegalStateException.class)
public void registerDuplicates() {
this.mapping.registerMapping("foo", this.handler, this.method1);
this.mapping.registerMapping("foo", this.handler, this.method2);
}
@Test
public void directMatch() throws Exception {
String key = "foo";
this.mapping.registerMapping(key, this.handler, this.method1);
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, key));
assertEquals(this.method1, ((HandlerMethod) result.block()).getMethod());
}
@Test
public void patternMatch() throws Exception {
this.mapping.registerMapping("/fo*", this.handler, this.method1);
this.mapping.registerMapping("/f*", this.handler, this.method2);
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, "/foo"));
assertEquals(this.method1, ((HandlerMethod) result.block()).getMethod());
}
@Test
public void ambiguousMatch() throws Exception {
this.mapping.registerMapping("/f?o", this.handler, this.method1);
this.mapping.registerMapping("/fo?", this.handler, this.method2);
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, "/foo"));
StepVerifier.create(result).expectError(IllegalStateException.class).verify();
}
@Test
public void registerMapping() throws Exception {
String key1 = "/foo";
String key2 = "/foo*";
this.mapping.registerMapping(key1, this.handler, this.method1);
this.mapping.registerMapping(key2, this.handler, this.method2);
List directUrlMatches = this.mapping.getMappingRegistry().getMappingsByUrl(key1);
assertNotNull(directUrlMatches);
assertEquals(1, directUrlMatches.size());
assertEquals(key1, directUrlMatches.get(0));
}
@Test
public void registerMappingWithSameMethodAndTwoHandlerInstances() throws Exception {
String key1 = "foo";
String key2 = "bar";
MyHandler handler1 = new MyHandler();
MyHandler handler2 = new MyHandler();
this.mapping.registerMapping(key1, handler1, this.method1);
this.mapping.registerMapping(key2, handler2, this.method1);
List directUrlMatches = this.mapping.getMappingRegistry().getMappingsByUrl(key1);
assertNotNull(directUrlMatches);
assertEquals(1, directUrlMatches.size());
assertEquals(key1, directUrlMatches.get(0));
}
@Test
public void unregisterMapping() throws Exception {
String key = "foo";
this.mapping.registerMapping(key, this.handler, this.method1);
Mono<Object> result = this.mapping.getHandler(createExchange(HttpMethod.GET, key));
assertNotNull(result.block());
this.mapping.unregisterMapping(key);
result = this.mapping.getHandler(createExchange(HttpMethod.GET, key));
assertNull(result.block());
assertNull(this.mapping.getMappingRegistry().getMappingsByUrl(key));
}
private ServerWebExchange createExchange(HttpMethod httpMethod, String path) throws URISyntaxException {
ServerHttpRequest request = MockServerHttpRequest.method(httpMethod, path).build();
WebSessionManager sessionManager = new MockWebSessionManager();
return new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
}
private static class MyHandlerMethodMapping extends AbstractHandlerMethodMapping<String> {
private PathMatcher pathMatcher = new AntPathMatcher();
@Override
protected boolean isHandler(Class<?> beanType) {
return true;
}
@Override
protected String getMappingForMethod(Method method, Class<?> handlerType) {
String methodName = method.getName();
return methodName.startsWith("handler") ? methodName : null;
}
@Override
protected Set<String> getMappingPathPatterns(String key) {
return (this.pathMatcher.isPattern(key) ? Collections.emptySet() : Collections.singleton(key));
}
@Override
protected String getMatchingMapping(String pattern, ServerWebExchange exchange) {
String lookupPath = exchange.getRequest().getURI().getPath();
return (this.pathMatcher.match(pattern, lookupPath) ? pattern : null);
}
@Override
protected Comparator<String> getMappingComparator(ServerWebExchange exchange) {
String lookupPath = exchange.getRequest().getURI().getPath();
return this.pathMatcher.getPatternComparator(lookupPath);
}
}
@Controller
private static class MyHandler {
@RequestMapping
@SuppressWarnings("unused")
public void handlerMethod1() {
}
@RequestMapping
@SuppressWarnings("unused")
public void handlerMethod2() {
}
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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.result.method;
import java.util.Collections;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.*;
/**
* Unit tests for {@link InvocableHandlerMethod}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
*/
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public class InvocableHandlerMethodTests {
private ServerWebExchange exchange;
@Before
public void setUp() throws Exception {
this.exchange = new DefaultServerWebExchange(
MockServerHttpRequest.get("http://localhost:8080/path").build(),
new MockServerHttpResponse(),
new MockWebSessionManager());
}
@Test
public void invokeMethodWithNoArguments() throws Exception {
InvocableHandlerMethod hm = handlerMethod("noArgs");
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
assertHandlerResultValue(mono, "success");
}
@Test
public void invokeMethodWithNoValue() throws Exception {
InvocableHandlerMethod hm = handlerMethod("singleArg");
addResolver(hm, Mono.empty());
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
assertHandlerResultValue(mono, "success:null");
}
@Test
public void invokeMethodWithValue() throws Exception {
InvocableHandlerMethod hm = handlerMethod("singleArg");
addResolver(hm, Mono.just("value1"));
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
assertHandlerResultValue(mono, "success:value1");
}
@Test
public void noMatchingResolver() throws Exception {
InvocableHandlerMethod hm = handlerMethod("singleArg");
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("No suitable resolver for argument 0 of type 'java.lang.String' " +
"on " + hm.getMethod().toGenericString()));
}
}
@Test
public void resolverThrowsException() throws Exception {
InvocableHandlerMethod hm = handlerMethod("singleArg");
addResolver(hm, Mono.error(new UnsupportedMediaTypeStatusException("boo")));
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
try {
mono.block();
fail("Expected UnsupportedMediaTypeStatusException");
}
catch (UnsupportedMediaTypeStatusException ex) {
assertThat(ex.getMessage(), is("Request failure [status: 415, reason: \"boo\"]"));
}
}
@Test
public void illegalArgumentExceptionIsWrappedWithInvocationDetails() throws Exception {
InvocableHandlerMethod hm = handlerMethod("singleArg");
addResolver(hm, Mono.just(1));
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("Failed to invoke handler method with resolved arguments: " +
"[0][type=java.lang.Integer][value=1] " +
"on " + hm.getMethod().toGenericString()));
}
}
@Test
public void invocationTargetExceptionIsUnwrapped() throws Exception {
InvocableHandlerMethod hm = handlerMethod("exceptionMethod");
Mono<HandlerResult> mono = hm.invoke(this.exchange, new BindingContext());
try {
mono.block();
fail("Expected IllegalStateException");
}
catch (IllegalStateException ex) {
assertThat(ex.getMessage(), is("boo"));
}
}
private InvocableHandlerMethod handlerMethod(String name) throws Exception {
TestController controller = new TestController();
return ResolvableMethod.on(controller).name(name).resolveHandlerMethod();
}
private void addResolver(InvocableHandlerMethod handlerMethod, Mono<Object> resolvedValue) {
HandlerMethodArgumentResolver resolver = mock(HandlerMethodArgumentResolver.class);
when(resolver.supportsParameter(any())).thenReturn(true);
when(resolver.resolveArgument(any(), any(), any())).thenReturn(resolvedValue);
handlerMethod.setArgumentResolvers(Collections.singletonList(resolver));
}
private void assertHandlerResultValue(Mono<HandlerResult> mono, String expected) {
StepVerifier.create(mono)
.consumeNextWith(result -> {
Optional<?> optional = result.getReturnValue();
assertTrue(optional.isPresent());
assertEquals(expected, optional.get());
})
.expectComplete()
.verify();
}
@SuppressWarnings("unused")
private static class TestController {
public String noArgs() {
return "success";
}
public String singleArg(String q) {
return "success:" + q;
}
public void exceptionMethod() {
throw new IllegalStateException("boo");
}
}
}

View File

@@ -0,0 +1,551 @@
/*
* 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.web.reactive.result.method;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.reactive.result.method.RequestMappingInfo.BuilderConfiguration;
import org.springframework.web.server.MethodNotAllowedException;
import org.springframework.web.server.NotAcceptableStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.support.HttpRequestPathHelper;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.HEAD;
import static org.springframework.web.bind.annotation.RequestMethod.OPTIONS;
import static org.springframework.web.reactive.result.method.RequestMappingInfo.paths;
/**
* Unit tests for {@link RequestMappingInfoHandlerMapping}.
* @author Rossen Stoyanchev
*/
public class RequestMappingInfoHandlerMappingTests {
private TestRequestMappingInfoHandlerMapping handlerMapping;
private ServerHttpRequest request;
@Before
public void setUp() throws Exception {
this.handlerMapping = new TestRequestMappingInfoHandlerMapping();
this.handlerMapping.registerHandler(new TestController());
}
@Test
public void getMappingPathPatterns() throws Exception {
String[] patterns = {"/foo/*", "/foo", "/bar/*", "/bar"};
RequestMappingInfo info = paths(patterns).build();
Set<String> actual = this.handlerMapping.getMappingPathPatterns(info);
assertEquals(new HashSet<>(Arrays.asList(patterns)), actual);
}
@Test
public void getHandlerDirectMatch() throws Exception {
String[] patterns = new String[] {"/foo"};
String[] params = new String[] {};
Method expected = resolveMethod(new TestController(), patterns, null, params);
this.request = MockServerHttpRequest.get("/foo").build();
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(createExchange()).block();
assertEquals(expected, hm.getMethod());
}
@Test
public void getHandlerGlobMatch() throws Exception {
String[] patterns = new String[] {"/ba*"};
RequestMethod[] methods = new RequestMethod[] {GET, HEAD};
Method expected = resolveMethod(new TestController(), patterns, methods, null);
this.request = MockServerHttpRequest.get("/bar").build();
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(createExchange()).block();
assertEquals(expected, hm.getMethod());
}
@Test
public void getHandlerEmptyPathMatch() throws Exception {
String[] patterns = new String[] {""};
Method expected = resolveMethod(new TestController(), patterns, null, null);
this.request = MockServerHttpRequest.get("").build();
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(createExchange()).block();
assertEquals(expected, hm.getMethod());
this.request = MockServerHttpRequest.get("/").build();
hm = (HandlerMethod) this.handlerMapping.getHandler(createExchange()).block();
assertEquals(expected, hm.getMethod());
}
@Test
public void getHandlerBestMatch() throws Exception {
String[] patterns = new String[] {"/foo"};
String[] params = new String[] {"p"};
Method expected = resolveMethod(new TestController(), patterns, null, params);
this.request = MockServerHttpRequest.get("/foo?p=anything").build();
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(createExchange()).block();
assertEquals(expected, hm.getMethod());
}
@Test
public void getHandlerRequestMethodNotAllowed() throws Exception {
this.request = MockServerHttpRequest.post("/bar").build();
Mono<Object> mono = this.handlerMapping.getHandler(createExchange());
assertError(mono, MethodNotAllowedException.class,
ex -> assertEquals(new HashSet<>(Arrays.asList("GET", "HEAD")), ex.getSupportedMethods()));
}
@Test // SPR-9603
public void getHandlerRequestMethodMatchFalsePositive() throws Exception {
this.request = MockServerHttpRequest.get("/users").accept(MediaType.APPLICATION_XML).build();
this.handlerMapping.registerHandler(new UserController());
Mono<Object> mono = this.handlerMapping.getHandler(createExchange());
StepVerifier.create(mono)
.expectError(NotAcceptableStatusException.class)
.verify();
}
@Test // SPR-8462
public void getHandlerMediaTypeNotSupported() throws Exception {
testHttpMediaTypeNotSupportedException("/person/1");
testHttpMediaTypeNotSupportedException("/person/1/");
testHttpMediaTypeNotSupportedException("/person/1.json");
}
@Test
public void getHandlerTestInvalidContentType() throws Exception {
this.request = MockServerHttpRequest.put("/person/1").header("content-type", "bogus").build();
Mono<Object> mono = this.handlerMapping.getHandler(createExchange());
assertError(mono, UnsupportedMediaTypeStatusException.class,
ex -> assertEquals("Request failure [status: 415, " +
"reason: \"Invalid mime type \"bogus\": does not contain '/'\"]",
ex.getMessage()));
}
@Test // SPR-8462
public void getHandlerTestMediaTypeNotAcceptable() throws Exception {
testMediaTypeNotAcceptable("/persons");
testMediaTypeNotAcceptable("/persons/");
testMediaTypeNotAcceptable("/persons.json");
}
@Test // SPR-12854
public void getHandlerTestRequestParamMismatch() throws Exception {
this.request = MockServerHttpRequest.get("/params").build();
Mono<Object> mono = this.handlerMapping.getHandler(createExchange());
assertError(mono, ServerWebInputException.class, ex -> {
assertThat(ex.getReason(), containsString("[foo=bar]"));
assertThat(ex.getReason(), containsString("[bar=baz]"));
});
}
@Test
public void getHandlerHttpOptions() throws Exception {
testHttpOptions("/foo", "GET,HEAD");
testHttpOptions("/person/1", "PUT");
testHttpOptions("/persons", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS");
testHttpOptions("/something", "PUT,POST");
}
@Test
public void getHandlerProducibleMediaTypesAttribute() throws Exception {
this.request = MockServerHttpRequest.get("/content").accept(MediaType.APPLICATION_XML).build();
ServerWebExchange exchange = createExchange();
this.handlerMapping.getHandler(exchange).block();
String name = HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
assertEquals(Collections.singleton(MediaType.APPLICATION_XML), exchange.getAttributes().get(name));
this.request = MockServerHttpRequest.get("/content").accept(MediaType.APPLICATION_JSON).build();
exchange = createExchange();
this.handlerMapping.getHandler(exchange).block();
assertNull("Negated expression shouldn't be listed as producible type",
exchange.getAttributes().get(name));
}
@Test @SuppressWarnings("unchecked")
public void handleMatchUriTemplateVariables() throws Exception {
String lookupPath = "/1/2";
this.request = MockServerHttpRequest.get(lookupPath).build();
ServerWebExchange exchange = createExchange();
RequestMappingInfo key = paths("/{path1}/{path2}").build();
this.handlerMapping.handleMatch(key, lookupPath, exchange);
String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
Map<String, String> uriVariables = (Map<String, String>) exchange.getAttributes().get(name);
assertNotNull(uriVariables);
assertEquals("1", uriVariables.get("path1"));
assertEquals("2", uriVariables.get("path2"));
}
@Test // SPR-9098
public void handleMatchUriTemplateVariablesDecode() throws Exception {
RequestMappingInfo key = paths("/{group}/{identifier}").build();
this.request = MockServerHttpRequest.method(HttpMethod.GET, URI.create("/group/a%2Fb")).build();
ServerWebExchange exchange = createExchange();
HttpRequestPathHelper pathHelper = new HttpRequestPathHelper();
pathHelper.setUrlDecode(false);
String lookupPath = pathHelper.getLookupPathForRequest(exchange);
this.handlerMapping.setPathHelper(pathHelper);
this.handlerMapping.handleMatch(key, lookupPath, exchange);
String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
@SuppressWarnings("unchecked")
Map<String, String> uriVariables = (Map<String, String>) exchange.getAttributes().get(name);
assertNotNull(uriVariables);
assertEquals("group", uriVariables.get("group"));
assertEquals("a/b", uriVariables.get("identifier"));
}
@Test
public void handleMatchBestMatchingPatternAttribute() throws Exception {
RequestMappingInfo key = paths("/{path1}/2", "/**").build();
this.request = MockServerHttpRequest.get("/1/2").build();
ServerWebExchange exchange = createExchange();
this.handlerMapping.handleMatch(key, "/1/2", exchange);
assertEquals("/{path1}/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
}
@Test
public void handleMatchBestMatchingPatternAttributeNoPatternsDefined() throws Exception {
RequestMappingInfo key = paths().build();
this.request = MockServerHttpRequest.get("/1/2").build();
ServerWebExchange exchange = createExchange();
this.handlerMapping.handleMatch(key, "/1/2", exchange);
assertEquals("/1/2", exchange.getAttributes().get(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
}
@Test
public void handleMatchMatrixVariables() throws Exception {
ServerWebExchange exchange;
MultiValueMap<String, String> matrixVariables;
Map<String, String> uriVariables;
this.request = MockServerHttpRequest.get("/").build();
exchange = createExchange();
handleMatch(exchange, "/{cars}", "/cars;colors=red,blue,green;year=2012");
matrixVariables = getMatrixVariables(exchange, "cars");
uriVariables = getUriTemplateVariables(exchange);
assertNotNull(matrixVariables);
assertEquals(Arrays.asList("red", "blue", "green"), matrixVariables.get("colors"));
assertEquals("2012", matrixVariables.getFirst("year"));
assertEquals("cars", uriVariables.get("cars"));
this.request = MockServerHttpRequest.get("/").build();
exchange = createExchange();
handleMatch(exchange, "/{cars:[^;]+}{params}", "/cars;colors=red,blue,green;year=2012");
matrixVariables = getMatrixVariables(exchange, "params");
uriVariables = getUriTemplateVariables(exchange);
assertNotNull(matrixVariables);
assertEquals(Arrays.asList("red", "blue", "green"), matrixVariables.get("colors"));
assertEquals("2012", matrixVariables.getFirst("year"));
assertEquals("cars", uriVariables.get("cars"));
assertEquals(";colors=red,blue,green;year=2012", uriVariables.get("params"));
this.request = MockServerHttpRequest.get("/").build();
exchange = createExchange();
handleMatch(exchange, "/{cars:[^;]+}{params}", "/cars");
matrixVariables = getMatrixVariables(exchange, "params");
uriVariables = getUriTemplateVariables(exchange);
assertNull(matrixVariables);
assertEquals("cars", uriVariables.get("cars"));
assertEquals("", uriVariables.get("params"));
}
@Test
public void handleMatchMatrixVariablesDecoding() throws Exception {
HttpRequestPathHelper urlPathHelper = new HttpRequestPathHelper();
urlPathHelper.setUrlDecode(false);
this.handlerMapping.setPathHelper(urlPathHelper);
this.request = MockServerHttpRequest.get("/").build();
ServerWebExchange exchange = createExchange();
handleMatch(exchange, "/path{filter}", "/path;mvar=a%2fb");
MultiValueMap<String, String> matrixVariables = getMatrixVariables(exchange, "filter");
Map<String, String> uriVariables = getUriTemplateVariables(exchange);
assertNotNull(matrixVariables);
assertEquals(Collections.singletonList("a/b"), matrixVariables.get("mvar"));
assertEquals(";mvar=a/b", uriVariables.get("filter"));
}
private ServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
@SuppressWarnings("unchecked")
private <T> void assertError(Mono<Object> mono, final Class<T> exceptionClass, final Consumer<T> consumer) {
StepVerifier.create(mono)
.consumeErrorWith(error -> {
assertEquals(exceptionClass, error.getClass());
consumer.accept((T) error);
})
.verify();
}
private void testHttpMediaTypeNotSupportedException(String url) throws Exception {
this.request = MockServerHttpRequest.put(url).contentType(MediaType.APPLICATION_JSON).build();
Mono<Object> mono = this.handlerMapping.getHandler(createExchange());
assertError(mono, UnsupportedMediaTypeStatusException.class, ex ->
assertEquals("Invalid supported consumable media types",
Collections.singletonList(new MediaType("application", "xml")),
ex.getSupportedMediaTypes()));
}
private void testHttpOptions(String requestURI, String allowHeader) throws Exception {
this.request = MockServerHttpRequest.options(requestURI).build();
ServerWebExchange exchange = createExchange();
HandlerMethod handlerMethod = (HandlerMethod) this.handlerMapping.getHandler(createExchange()).block();
BindingContext bindingContext = new BindingContext();
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
Mono<HandlerResult> mono = invocable.invoke(exchange, bindingContext);
HandlerResult result = mono.block();
assertNotNull(result);
Optional<Object> value = result.getReturnValue();
assertTrue(value.isPresent());
assertEquals(HttpHeaders.class, value.get().getClass());
assertEquals(allowHeader, ((HttpHeaders) value.get()).getFirst("Allow"));
}
private void testMediaTypeNotAcceptable(String url) throws Exception {
this.request = MockServerHttpRequest.get(url).accept(MediaType.APPLICATION_JSON).build();
Mono<Object> mono = this.handlerMapping.getHandler(createExchange());
assertError(mono, NotAcceptableStatusException.class, ex ->
assertEquals("Invalid supported producible media types",
Collections.singletonList(new MediaType("application", "xml")),
ex.getSupportedMediaTypes()));
}
private void handleMatch(ServerWebExchange exchange, String pattern, String lookupPath) {
RequestMappingInfo info = paths(pattern).build();
this.handlerMapping.handleMatch(info, lookupPath, exchange);
}
@SuppressWarnings("unchecked")
private MultiValueMap<String, String> getMatrixVariables(ServerWebExchange exchange, String uriVarName) {
String attrName = HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE;
return ((Map<String, MultiValueMap<String, String>>) exchange.getAttributes().get(attrName)).get(uriVarName);
}
@SuppressWarnings("unchecked")
private Map<String, String> getUriTemplateVariables(ServerWebExchange exchange) {
String attrName = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
return (Map<String, String>) exchange.getAttributes().get(attrName);
}
private Method resolveMethod(Object controller, String[] patterns,
RequestMethod[] methods, String[] params) {
return ResolvableMethod.on(controller)
.matching(method -> {
RequestMapping annot = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
if (annot == null) {
return false;
}
else if (patterns != null && !Arrays.equals(annot.path(), patterns)) {
return false;
}
else if (methods != null && !Arrays.equals(annot.method(), methods)) {
return false;
}
else if (params != null && (!Arrays.equals(annot.params(), params))) {
return false;
}
return true;
})
.resolve();
}
@SuppressWarnings("unused")
@Controller
private static class TestController {
@GetMapping("/foo")
public void foo() {
}
@GetMapping(path = "/foo", params="p")
public void fooParam() {
}
@RequestMapping(path = "/ba*", method = { GET, HEAD })
public void bar() {
}
@RequestMapping(path = "")
public void empty() {
}
@PutMapping(path = "/person/{id}", consumes="application/xml")
public void consumes(@RequestBody String text) {
}
@RequestMapping(path = "/persons", produces="application/xml")
public String produces() {
return "";
}
@RequestMapping(path = "/params", params="foo=bar")
public String param() {
return "";
}
@RequestMapping(path = "/params", params="bar=baz")
public String param2() {
return "";
}
@RequestMapping(path = "/content", produces="application/xml")
public String xmlContent() {
return "";
}
@RequestMapping(path = "/content", produces="!application/xml")
public String nonXmlContent() {
return "";
}
@RequestMapping(path = "/something", method = OPTIONS)
public HttpHeaders fooOptions() {
HttpHeaders headers = new HttpHeaders();
headers.add("Allow", "PUT,POST");
return headers;
}
}
@SuppressWarnings("unused")
@Controller
private static class UserController {
@GetMapping(path = "/users", produces = "application/json")
public void getUser() {
}
@PutMapping(path = "/users")
public void saveUser() {
}
}
private static class TestRequestMappingInfoHandlerMapping extends RequestMappingInfoHandlerMapping {
void registerHandler(Object handler) {
super.detectHandlerMethods(handler);
}
@Override
protected boolean isHandler(Class<?> beanType) {
return AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null;
}
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMapping annot = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
if (annot != null) {
BuilderConfiguration options = new BuilderConfiguration();
options.setPathHelper(getPathHelper());
options.setPathMatcher(getPathMatcher());
options.setSuffixPatternMatch(true);
options.setTrailingSlashMatch(true);
return paths(annot.value()).methods(annot.method())
.params(annot.params()).headers(annot.headers())
.consumes(annot.consumes()).produces(annot.produces())
.options(options).build();
}
else {
return null;
}
}
}
}

View File

@@ -0,0 +1,153 @@
/*
* 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.web.reactive.result.method.annotation;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.springframework.http.RequestEntity.get;
import static org.springframework.http.RequestEntity.options;
import static org.springframework.http.RequestEntity.post;
/**
* Base class for integration tests with {@code @RequestMapping methods}.
*
* @author Rossen Stoyanchev
*/
public abstract class AbstractRequestMappingIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private RestTemplate restTemplate = new RestTemplate();
private ApplicationContext applicationContext;
@Override
protected HttpHandler createHttpHandler() {
this.restTemplate = initRestTemplate();
this.applicationContext = initApplicationContext();
return WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
}
protected abstract ApplicationContext initApplicationContext();
protected RestTemplate initRestTemplate() {
return new RestTemplate();
}
protected ApplicationContext getApplicationContext() {
return this.applicationContext;
}
protected RestTemplate getRestTemplate() {
return this.restTemplate;
}
<T> ResponseEntity<T> performGet(String url, MediaType out, Class<T> type) throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(out));
return getRestTemplate().exchange(prepareGet(url, headers), type);
}
<T> ResponseEntity<T> performGet(String url, HttpHeaders headers, Class<T> type) throws Exception {
return getRestTemplate().exchange(prepareGet(url, headers), type);
}
<T> ResponseEntity<T> performGet(String url, MediaType out, ParameterizedTypeReference<T> type)
throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(out));
return this.restTemplate.exchange(prepareGet(url, headers), type);
}
<T> ResponseEntity<T> performOptions(String url, HttpHeaders headers, Class<T> type)
throws Exception {
return getRestTemplate().exchange(prepareOptions(url, headers), type);
}
<T> ResponseEntity<T> performPost(String url, MediaType in, Object body, MediaType out, Class<T> type)
throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(in);
if (out != null) {
headers.setAccept(Collections.singletonList(out));
}
return getRestTemplate().exchange(preparePost(url, headers, body), type);
}
<T> ResponseEntity<T> performPost(String url, HttpHeaders headers, Object body,
Class<T> type) throws Exception {
return getRestTemplate().exchange(preparePost(url, headers, body), type);
}
<T> ResponseEntity<T> performPost(String url, MediaType in, Object body, MediaType out,
ParameterizedTypeReference<T> type) throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(in);
if (out != null) {
headers.setAccept(Collections.singletonList(out));
}
return getRestTemplate().exchange(preparePost(url, headers, body), type);
}
private RequestEntity<Void> prepareGet(String url, HttpHeaders headers) throws Exception {
URI uri = new URI("http://localhost:" + this.port + url);
RequestEntity.HeadersBuilder<?> builder = get(uri);
addHeaders(builder, headers);
return builder.build();
}
private RequestEntity<Void> prepareOptions(String url, HttpHeaders headers) throws Exception {
URI uri = new URI("http://localhost:" + this.port + url);
RequestEntity.HeadersBuilder<?> builder = options(uri);
addHeaders(builder, headers);
return builder.build();
}
private void addHeaders(RequestEntity.HeadersBuilder<?> builder, HttpHeaders headers) {
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
for (String value : entry.getValue()) {
builder.header(entry.getKey(), value);
}
}
}
private RequestEntity<?> preparePost(String url, HttpHeaders headers, Object body) throws Exception {
URI uri = new URI("http://localhost:" + this.port + url);
RequestEntity.BodyBuilder builder = post(uri);
addHeaders(builder, headers);
return builder.body(body);
}
}

View File

@@ -0,0 +1,176 @@
/*
* 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.web.reactive.result.method.annotation;
import java.util.Collections;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import rx.Single;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.ui.Model;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.WebExchangeDataBinder;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.config.WebReactiveConfigurationSupport;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
/**
* Unit tests for {@link BindingContextFactory}.
* @author Rossen Stoyanchev
*/
public class BindingContextFactoryTests {
private BindingContextFactory contextFactory;
private ServerWebExchange exchange;
@Before
public void setUp() throws Exception {
WebReactiveConfigurationSupport configurationSupport = new WebReactiveConfigurationSupport();
configurationSupport.setApplicationContext(new StaticApplicationContext());
RequestMappingHandlerAdapter adapter = configurationSupport.requestMappingHandlerAdapter();
adapter.afterPropertiesSet();
this.contextFactory = new BindingContextFactory(adapter);
MockServerHttpRequest request = MockServerHttpRequest.get("/path").build();
MockServerHttpResponse response = new MockServerHttpResponse();
this.exchange = new DefaultServerWebExchange(request, response);
}
@SuppressWarnings("unchecked")
@Test
public void basic() throws Exception {
Validator validator = mock(Validator.class);
TestController controller = new TestController(validator);
HandlerMethod handlerMethod = ResolvableMethod.on(controller)
.annotated(RequestMapping.class)
.resolveHandlerMethod();
BindingContext bindingContext =
this.contextFactory.createBindingContext(handlerMethod, this.exchange)
.blockMillis(5000);
WebExchangeDataBinder binder = bindingContext.createDataBinder(this.exchange, "name");
assertEquals(Collections.singletonList(validator), binder.getValidators());
Map<String, Object> model = bindingContext.getModel().asMap();
assertEquals(5, model.size());
Object value = model.get("bean");
assertEquals("Bean", ((TestBean) value).getName());
value = model.get("monoBean");
assertEquals("Mono Bean", ((Mono<TestBean>) value).blockMillis(5000).getName());
value = model.get("singleBean");
assertEquals("Single Bean", ((Single<TestBean>) value).toBlocking().value().getName());
value = model.get("voidMethodBean");
assertEquals("Void Method Bean", ((TestBean) value).getName());
value = model.get("voidMonoMethodBean");
assertEquals("Void Mono Method Bean", ((TestBean) value).getName());
}
@SuppressWarnings("unused")
private static class TestController {
private Validator[] validators;
public TestController(Validator... validators) {
this.validators = validators;
}
@InitBinder
public void initDataBinder(WebDataBinder dataBinder) {
if (!ObjectUtils.isEmpty(this.validators)) {
dataBinder.addValidators(this.validators);
}
}
@ModelAttribute("bean")
public TestBean returnValue() {
return new TestBean("Bean");
}
@ModelAttribute("monoBean")
public Mono<TestBean> returnValueMono() {
return Mono.just(new TestBean("Mono Bean"));
}
@ModelAttribute("singleBean")
public Single<TestBean> returnValueSingle() {
return Single.just(new TestBean("Single Bean"));
}
@ModelAttribute
public void voidMethodBean(Model model) {
model.addAttribute("voidMethodBean", new TestBean("Void Method Bean"));
}
@ModelAttribute
public Mono<Void> voidMonoMethodBean(Model model) {
return Mono.just("Void Mono Method Bean")
.doOnNext(name -> model.addAttribute("voidMonoMethodBean", new TestBean(name)))
.then();
}
@RequestMapping
public void handle() {}
}
private static class TestBean {
private final String name;
TestBean(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public String toString() {
return "TestBean[name=" + this.name + "]";
}
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.web.reactive.result.method.annotation;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.bootstrap.ReactorHttpServer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.config.EnableWebReactive;
import static org.junit.Assert.assertEquals;
/**
* Integration tests that demonstrate running multiple applications under
* different context paths.
*
* @author Rossen Stoyanchev
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public class ContextPathIntegrationTests {
private ReactorHttpServer server;
@Before
public void setUp() throws Exception {
AnnotationConfigApplicationContext context1 = new AnnotationConfigApplicationContext();
context1.register(WebApp1Config.class);
context1.refresh();
AnnotationConfigApplicationContext context2 = new AnnotationConfigApplicationContext();
context2.register(WebApp2Config.class);
context2.refresh();
HttpHandler webApp1Handler = DispatcherHandler.toHttpHandler(context1);
HttpHandler webApp2Handler = DispatcherHandler.toHttpHandler(context2);
this.server = new ReactorHttpServer();
this.server.registerHttpHandler("/webApp1", webApp1Handler);
this.server.registerHttpHandler("/webApp2", webApp2Handler);
this.server.afterPropertiesSet();
this.server.start();
}
@After
public void tearDown() throws Exception {
this.server.stop();
}
@Test
public void basic() throws Exception {
RestTemplate restTemplate = new RestTemplate();
String actual;
actual = restTemplate.getForObject(createUrl("/webApp1/test"), String.class);
assertEquals("Tested in /webApp1", actual);
actual = restTemplate.getForObject(createUrl("/webApp2/test"), String.class);
assertEquals("Tested in /webApp2", actual);
}
private String createUrl(String path) {
return "http://localhost:" + this.server.getPort() + path;
}
@EnableWebReactive
@Configuration
static class WebApp1Config {
@Bean
public TestController testController() {
return new TestController();
}
}
@EnableWebReactive
@Configuration
static class WebApp2Config {
@Bean
public TestController testController() {
return new TestController();
}
}
@RestController
static class TestController {
@GetMapping("/test")
public String handle(ServerHttpRequest request) {
return "Tested in " + request.getContextPath();
}
}
}

View File

@@ -0,0 +1,136 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.http.HttpCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Test fixture with {@link CookieValueMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class CookieValueMethodArgumentResolverTests {
private CookieValueMethodArgumentResolver resolver;
private ServerHttpRequest request;
private MethodParameter cookieParameter;
private MethodParameter cookieStringParameter;
private MethodParameter stringParameter;
private BindingContext bindingContext = new BindingContext();
@Before
public void setUp() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();
this.resolver = new CookieValueMethodArgumentResolver(context.getBeanFactory());
this.request = MockServerHttpRequest.get("/").build();
Method method = getClass().getMethod("params", HttpCookie.class, String.class, String.class);
this.cookieParameter = new SynthesizingMethodParameter(method, 0);
this.cookieStringParameter = new SynthesizingMethodParameter(method, 1);
this.stringParameter = new SynthesizingMethodParameter(method, 2);
}
@Test
public void supportsParameter() {
assertTrue(this.resolver.supportsParameter(this.cookieParameter));
assertTrue(this.resolver.supportsParameter(this.cookieStringParameter));
assertFalse(this.resolver.supportsParameter(this.stringParameter));
}
@Test
public void resolveCookieArgument() {
HttpCookie expected = new HttpCookie("name", "foo");
this.request = MockServerHttpRequest.get("/").cookie(expected.getName(), expected).build();
Mono<Object> mono = this.resolver.resolveArgument(
this.cookieParameter, this.bindingContext, createExchange());
assertEquals(expected, mono.block());
}
@Test
public void resolveCookieStringArgument() {
HttpCookie cookie = new HttpCookie("name", "foo");
this.request = MockServerHttpRequest.get("/").cookie(cookie.getName(), cookie).build();
Mono<Object> mono = this.resolver.resolveArgument(
this.cookieStringParameter, this.bindingContext, createExchange());
assertEquals("Invalid result", cookie.getValue(), mono.block());
}
@Test
public void resolveCookieDefaultValue() {
Object result = this.resolver.resolveArgument(
this.cookieStringParameter, this.bindingContext, createExchange()).block();
assertTrue(result instanceof String);
assertEquals("bar", result);
}
@Test
public void notFound() {
Mono<Object> mono = resolver.resolveArgument(this.cookieParameter, this.bindingContext, createExchange());
StepVerifier.create(mono)
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
@SuppressWarnings("unused")
public void params(
@CookieValue("name") HttpCookie cookie,
@CookieValue(name = "name", defaultValue = "bar") String cookieString,
String stringParam) {
}
}

View File

@@ -0,0 +1,325 @@
/*
* 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.web.reactive.result.method.annotation;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.config.EnableWebReactive;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Integration tests with {@code @CrossOrigin} and {@code @RequestMapping}
* annotated handler methods.
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
*/
public class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegrationTests {
private HttpHeaders headers;
@Before
public void setup() throws Exception {
super.setup();
this.headers = new HttpHeaders();
this.headers.setOrigin("http://site1.com");
}
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(WebConfig.class);
Properties props = new Properties();
props.setProperty("myOrigin", "http://site1.com");
context.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props));
context.register(PropertySourcesPlaceholderConfigurer.class);
context.refresh();
return context;
}
@Override
protected RestTemplate initRestTemplate() {
// JDK default HTTP client blacklist headers like Origin
return new RestTemplate(new HttpComponentsClientHttpRequestFactory());
}
@Test
public void actualGetRequestWithoutAnnotation() throws Exception {
ResponseEntity<String> entity = performGet("/no", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("no", entity.getBody());
}
@Test
public void actualPostRequestWithoutAnnotation() throws Exception {
ResponseEntity<String> entity = performPost("/no", this.headers, null, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("no-post", entity.getBody());
}
@Test
public void actualRequestWithDefaultAnnotation() throws Exception {
ResponseEntity<String> entity = performGet("/default", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("default", entity.getBody());
}
@Test
public void preflightRequestWithDefaultAnnotation() throws Exception {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
ResponseEntity<Void> entity = performOptions("/default", this.headers, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(1800, entity.getHeaders().getAccessControlMaxAge());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
}
@Test
public void actualRequestWithDefaultAnnotationAndNoOrigin() throws Exception {
HttpHeaders headers = new HttpHeaders();
ResponseEntity<String> entity = performGet("/default", headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("default", entity.getBody());
}
@Test
public void actualRequestWithCustomizedAnnotation() throws Exception {
ResponseEntity<String> entity = performGet("/customized", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals(-1, entity.getHeaders().getAccessControlMaxAge());
assertEquals("customized", entity.getBody());
}
@Test
public void preflightRequestWithCustomizedAnnotation() throws Exception {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1, header2");
ResponseEntity<String> entity = performOptions("/customized", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertArrayEquals(new HttpMethod[] {HttpMethod.GET},
entity.getHeaders().getAccessControlAllowMethods().toArray());
assertArrayEquals(new String[] {"header1", "header2"},
entity.getHeaders().getAccessControlAllowHeaders().toArray());
assertArrayEquals(new String[] {"header3", "header4"},
entity.getHeaders().getAccessControlExposeHeaders().toArray());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals(123, entity.getHeaders().getAccessControlMaxAge());
}
@Test
public void customOriginDefinedViaValueAttribute() throws Exception {
ResponseEntity<String> entity = performGet("/origin-value-attribute", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("value-attribute", entity.getBody());
}
@Test
public void customOriginDefinedViaPlaceholder() throws Exception {
ResponseEntity<String> entity = performGet("/origin-placeholder", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("placeholder", entity.getBody());
}
@Test
public void classLevel() throws Exception {
ResponseEntity<String> entity = performGet("/foo", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("*", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("foo", entity.getBody());
entity = performGet("/bar", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("*", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("bar", entity.getBody());
entity = performGet("/baz", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("baz", entity.getBody());
}
@Test
public void ambiguousHeaderPreflightRequest() throws Exception {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1");
ResponseEntity<String> entity = performOptions("/ambiguous-header", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertArrayEquals(new HttpMethod[] {HttpMethod.GET},
entity.getHeaders().getAccessControlAllowMethods().toArray());
assertArrayEquals(new String[] {"header1"},
entity.getHeaders().getAccessControlAllowHeaders().toArray());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
}
@Test
public void ambiguousProducesPreflightRequest() throws Exception {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
ResponseEntity<String> entity = performOptions("/ambiguous-produces", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertArrayEquals(new HttpMethod[] {HttpMethod.GET},
entity.getHeaders().getAccessControlAllowMethods().toArray());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
}
@Configuration
@EnableWebReactive
@ComponentScan(resourcePattern = "**/CrossOriginAnnotationIntegrationTests*")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig {
}
@RestController @SuppressWarnings("unused")
private static class MethodLevelController {
@GetMapping("/no")
public String noAnnotation() {
return "no";
}
@PostMapping("/no")
public String noAnnotationPost() {
return "no-post";
}
@CrossOrigin
@GetMapping("/default")
public String defaultAnnotation() {
return "default";
}
@CrossOrigin
@GetMapping(path = "/default", params = "q")
public void defaultAnnotationWithParams() {
}
@CrossOrigin
@GetMapping(path = "/ambiguous-header", headers = "header1=a")
public void ambigousHeader1a() {
}
@CrossOrigin
@GetMapping(path = "/ambiguous-header", headers = "header1=b")
public void ambigousHeader1b() {
}
@CrossOrigin
@GetMapping(path = "/ambiguous-produces", produces = "application/xml")
public String ambigousProducesXml() {
return "<a></a>";
}
@CrossOrigin
@GetMapping(path = "/ambiguous-produces", produces = "application/json")
public String ambigousProducesJson() {
return "{}";
}
@CrossOrigin(
origins = { "http://site1.com", "http://site2.com" },
allowedHeaders = { "header1", "header2" },
exposedHeaders = { "header3", "header4" },
methods = RequestMethod.GET,
maxAge = 123,
allowCredentials = "false")
@RequestMapping(path = "/customized", method = { RequestMethod.GET, RequestMethod.POST })
public String customized() {
return "customized";
}
@CrossOrigin("http://site1.com")
@GetMapping("/origin-value-attribute")
public String customOriginDefinedViaValueAttribute() {
return "value-attribute";
}
@CrossOrigin("${myOrigin}")
@GetMapping("/origin-placeholder")
public String customOriginDefinedViaPlaceholder() {
return "placeholder";
}
}
@RestController
@CrossOrigin(allowCredentials = "false")
@SuppressWarnings("unused")
private static class ClassLevelController {
@GetMapping("/foo")
public String foo() {
return "foo";
}
@CrossOrigin
@GetMapping("/bar")
public String bar() {
return "bar";
}
@CrossOrigin(allowCredentials = "true")
@GetMapping("/baz")
public String baz() {
return "baz";
}
}
}

View File

@@ -0,0 +1,159 @@
/*
* 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.web.reactive.result.method.annotation;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.WebExchangeDataBinder;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClass;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
/**
* Unit tests for {@link ErrorsMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class ErrorsArgumentResolverTests {
private ErrorsMethodArgumentResolver resolver ;
private final BindingContext bindingContext = new BindingContext();
private BindingResult bindingResult;
private ServerWebExchange exchange;
private final ResolvableMethod testMethod = ResolvableMethod.onClass(this.getClass()).name("handle");
@Before
public void setUp() throws Exception {
this.resolver = new ErrorsMethodArgumentResolver(new ReactiveAdapterRegistry());
MockServerHttpRequest request = MockServerHttpRequest.post("/path").build();
MockServerHttpResponse response = new MockServerHttpResponse();
this.exchange = new DefaultServerWebExchange(request, response);
Foo foo = new Foo();
WebExchangeDataBinder binder = this.bindingContext.createDataBinder(this.exchange, foo, "foo");
this.bindingResult = binder.getBindingResult();
}
@Test
public void supports() throws Exception {
MethodParameter parameter = parameter(forClass(Errors.class));
assertTrue(this.resolver.supportsParameter(parameter));
parameter = parameter(forClass(BindingResult.class));
assertTrue(this.resolver.supportsParameter(parameter));
parameter = parameter(forClassWithGenerics(Mono.class, Errors.class));
assertFalse(this.resolver.supportsParameter(parameter));
parameter = parameter(forClass(String.class));
assertFalse(this.resolver.supportsParameter(parameter));
}
@Test
public void resolveErrors() throws Exception {
testResolve(this.bindingResult);
}
@Test
public void resolveErrorsMono() throws Exception {
MonoProcessor<BindingResult> monoProcessor = MonoProcessor.create();
monoProcessor.onNext(this.bindingResult);
testResolve(monoProcessor);
}
@Test(expected = IllegalArgumentException.class)
public void resolveErrorsAfterMonoModelAttribute() throws Exception {
MethodParameter parameter = parameter(forClass(BindingResult.class));
this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange).blockMillis(5000);
}
private void testResolve(Object bindingResult) {
String key = BindingResult.MODEL_KEY_PREFIX + "foo";
this.bindingContext.getModel().asMap().put(key, bindingResult);
MethodParameter parameter = parameter(forClass(Errors.class));
Object actual = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange)
.blockMillis(5000);
assertSame(this.bindingResult, actual);
}
private MethodParameter parameter(ResolvableType type) {
return this.testMethod.resolveParam(type);
}
private static class Foo {
private String name;
public Foo() {
}
public Foo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@SuppressWarnings("unused")
void handle(
@ModelAttribute Foo foo,
Errors errors,
@ModelAttribute Mono<Foo> fooMono,
BindingResult bindingResult,
Mono<Errors> errorsMono,
String string) {}
}

View File

@@ -0,0 +1,98 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link ExpressionValueMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class ExpressionValueMethodArgumentResolverTests {
private ExpressionValueMethodArgumentResolver resolver;
private ServerWebExchange exchange;
private MethodParameter paramSystemProperty;
private MethodParameter paramNotSupported;
@Before
public void setUp() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();
this.resolver = new ExpressionValueMethodArgumentResolver(context.getBeanFactory());
ServerHttpRequest request = MockServerHttpRequest.get("/").build();
this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
Method method = getClass().getMethod("params", int.class, String.class);
this.paramSystemProperty = new MethodParameter(method, 0);
this.paramNotSupported = new MethodParameter(method, 1);
}
@Test
public void supportsParameter() throws Exception {
assertTrue(this.resolver.supportsParameter(this.paramSystemProperty));
assertFalse(this.resolver.supportsParameter(this.paramNotSupported));
}
@Test
public void resolveSystemProperty() throws Exception {
System.setProperty("systemProperty", "22");
try {
Mono<Object> mono = this.resolver.resolveArgument(
this.paramSystemProperty, new BindingContext(), this.exchange);
Object value = mono.block();
assertEquals(22, value);
}
finally {
System.clearProperty("systemProperty");
}
}
// TODO: test with expression for ServerWebExchange
@SuppressWarnings("unused")
public void params(@Value("#{systemProperties.systemProperty}") int param1, String notSupported) {
}
}

View File

@@ -0,0 +1,168 @@
/*
* 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.web.reactive.result.method.annotation;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.WebReactiveConfigurationSupport;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
/**
*
* Integration tests with {@code @RequestMapping} handler methods and global
* CORS configuration.
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
*/
public class GlobalCorsConfigIntegrationTests extends AbstractRequestMappingIntegrationTests {
private HttpHeaders headers;
@Before
public void setup() throws Exception {
super.setup();
this.headers = new HttpHeaders();
this.headers.setOrigin("http://localhost:9000");
}
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(WebConfig.class);
context.refresh();
return context;
}
@Override
protected RestTemplate initRestTemplate() {
// JDK default HTTP client blacklists headers like Origin
return new RestTemplate(new HttpComponentsClientHttpRequestFactory());
}
@Test
public void actualRequestWithCorsEnabled() throws Exception {
ResponseEntity<String> entity = performGet("/cors", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://localhost:9000", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("cors", entity.getBody());
}
@Test
public void actualRequestWithCorsRejected() throws Exception {
try {
performGet("/cors-restricted", this.headers, String.class);
fail();
}
catch (HttpClientErrorException e) {
assertEquals(HttpStatus.FORBIDDEN, e.getStatusCode());
}
}
@Test
public void actualRequestWithoutCorsEnabled() throws Exception {
ResponseEntity<String> entity = performGet("/welcome", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("welcome", entity.getBody());
}
@Test
public void preFlightRequestWithCorsEnabled() throws Exception {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
ResponseEntity<String> entity = performOptions("/cors", this.headers, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://localhost:9000", entity.getHeaders().getAccessControlAllowOrigin());
}
@Test
public void preFlightRequestWithCorsRejected() throws Exception {
try {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
performOptions("/cors-restricted", this.headers, String.class);
fail();
}
catch (HttpClientErrorException e) {
assertEquals(HttpStatus.FORBIDDEN, e.getStatusCode());
}
}
@Test
public void preFlightRequestWithoutCorsEnabled() throws Exception {
try {
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
performOptions("/welcome", this.headers, String.class);
fail();
}
catch (HttpClientErrorException e) {
assertEquals(HttpStatus.FORBIDDEN, e.getStatusCode());
}
}
@Configuration
@ComponentScan(resourcePattern = "**/GlobalCorsConfigIntegrationTests*.class")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig extends WebReactiveConfigurationSupport {
@Override
protected void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/cors-restricted").allowedOrigins("http://foo");
registry.addMapping("/cors");
}
}
@RestController @SuppressWarnings("unused")
static class TestController {
@GetMapping("/welcome")
public String welcome() {
return "welcome";
}
@GetMapping("/cors")
public String cors() {
return "cors";
}
@GetMapping("/cors-restricted")
public String corsRestricted() {
return "corsRestricted";
}
}
}

View File

@@ -0,0 +1,370 @@
/*
* 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.web.reactive.result.method.annotation;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import rx.Observable;
import rx.RxReactiveStreams;
import rx.Single;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
/**
* Unit tests for {@link HttpEntityArgumentResolver}.When adding a test also
* consider whether the logic under test is in a parent class, then see:
* {@link MessageReaderArgumentResolverTests}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
public class HttpEntityArgumentResolverTests {
private HttpEntityArgumentResolver resolver = createResolver();
private MockServerHttpRequest request;
private ResolvableMethod testMethod = ResolvableMethod.onClass(getClass()).name("handle");
@Before
public void setUp() throws Exception {
this.request = MockServerHttpRequest.post("/path").build();
}
private HttpEntityArgumentResolver createResolver() {
List<HttpMessageReader<?>> readers = new ArrayList<>();
readers.add(new DecoderHttpMessageReader<>(new StringDecoder()));
return new HttpEntityArgumentResolver(readers);
}
@Test
public void supports() throws Exception {
testSupports(httpEntityType(String.class));
testSupports(httpEntityType(forClassWithGenerics(Mono.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(Single.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(io.reactivex.Single.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(Maybe.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(CompletableFuture.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(Flux.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(Observable.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(io.reactivex.Observable.class, String.class)));
testSupports(httpEntityType(forClassWithGenerics(Flowable.class, String.class)));
testSupports(forClassWithGenerics(RequestEntity.class, String.class));
}
@Test
public void doesNotSupport() throws Exception {
ResolvableType type = ResolvableType.forClassWithGenerics(Mono.class, String.class);
assertFalse(this.resolver.supportsParameter(this.testMethod.resolveParam(type)));
type = ResolvableType.forClass(String.class);
assertFalse(this.resolver.supportsParameter(this.testMethod.resolveParam(type)));
}
@Test
public void emptyBodyWithString() throws Exception {
ResolvableType type = httpEntityType(String.class);
HttpEntity<Object> entity = resolveValueWithEmptyBody(type);
assertNull(entity.getBody());
}
@Test
public void emptyBodyWithMono() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Mono.class, String.class));
HttpEntity<Mono<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody()).expectNextCount(0).expectComplete().verify();
}
@Test
public void emptyBodyWithFlux() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Flux.class, String.class));
HttpEntity<Flux<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody()).expectNextCount(0).expectComplete().verify();
}
@Test
public void emptyBodyWithSingle() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Single.class, String.class));
HttpEntity<Single<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(RxReactiveStreams.toPublisher(entity.getBody()))
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
}
@Test
public void emptyBodyWithRxJava2Single() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(io.reactivex.Single.class, String.class));
HttpEntity<io.reactivex.Single<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody().toFlowable())
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
}
@Test
public void emptyBodyWithRxJava2Maybe() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Maybe.class, String.class));
HttpEntity<Maybe<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody().toFlowable())
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithObservable() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Observable.class, String.class));
HttpEntity<Observable<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(RxReactiveStreams.toPublisher(entity.getBody()))
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithRxJava2Observable() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(io.reactivex.Observable.class, String.class));
HttpEntity<io.reactivex.Observable<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody().toFlowable(BackpressureStrategy.BUFFER))
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithFlowable() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(Flowable.class, String.class));
HttpEntity<Flowable<String>> entity = resolveValueWithEmptyBody(type);
StepVerifier.create(entity.getBody())
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithCompletableFuture() throws Exception {
ResolvableType type = httpEntityType(forClassWithGenerics(CompletableFuture.class, String.class));
HttpEntity<CompletableFuture<String>> entity = resolveValueWithEmptyBody(type);
entity.getBody().whenComplete((body, ex) -> {
assertNull(body);
assertNull(ex);
});
}
@Test
public void httpEntityWithStringBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(String.class);
HttpEntity<String> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody());
}
@Test
public void httpEntityWithMonoBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(forClassWithGenerics(Mono.class, String.class));
HttpEntity<Mono<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody().block());
}
@Test
public void httpEntityWithSingleBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(forClassWithGenerics(Single.class, String.class));
HttpEntity<Single<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody().toBlocking().value());
}
@Test
public void httpEntityWithRxJava2SingleBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(forClassWithGenerics(io.reactivex.Single.class, String.class));
HttpEntity<io.reactivex.Single<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody().blockingGet());
}
@Test
public void httpEntityWithRxJava2MaybeBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(forClassWithGenerics(Maybe.class, String.class));
HttpEntity<Maybe<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody().blockingGet());
}
@Test
public void httpEntityWithCompletableFutureBody() throws Exception {
String body = "line1";
ResolvableType type = httpEntityType(forClassWithGenerics(CompletableFuture.class, String.class));
HttpEntity<CompletableFuture<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
assertEquals("line1", httpEntity.getBody().get());
}
@Test
public void httpEntityWithFluxBody() throws Exception {
String body = "line1\nline2\nline3\n";
ResolvableType type = httpEntityType(forClassWithGenerics(Flux.class, String.class));
HttpEntity<Flux<String>> httpEntity = resolveValue(type, body);
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
StepVerifier.create(httpEntity.getBody())
.expectNext("line1\n")
.expectNext("line2\n")
.expectNext("line3\n")
.expectComplete()
.verify();
}
@Test
public void requestEntity() throws Exception {
String body = "line1";
ResolvableType type = forClassWithGenerics(RequestEntity.class, String.class);
RequestEntity<String> requestEntity = resolveValue(type, body);
assertEquals(this.request.getMethod(), requestEntity.getMethod());
assertEquals(this.request.getURI(), requestEntity.getUrl());
assertEquals(this.request.getHeaders(), requestEntity.getHeaders());
assertEquals("line1", requestEntity.getBody());
}
private ResolvableType httpEntityType(Class<?> bodyType) {
return httpEntityType(ResolvableType.forClass(bodyType));
}
private ResolvableType httpEntityType(ResolvableType type) {
return forClassWithGenerics(HttpEntity.class, type);
}
private void testSupports(ResolvableType type) {
MethodParameter parameter = this.testMethod.resolveParam(type);
assertTrue(this.resolver.supportsParameter(parameter));
}
@SuppressWarnings("unchecked")
private <T> T resolveValue(ResolvableType type, String body) {
this.request = MockServerHttpRequest.post("/path").header("foo", "bar")
.contentType(MediaType.TEXT_PLAIN)
.body(body);
ServerWebExchange exchange = new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
MethodParameter param = this.testMethod.resolveParam(type);
Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange);
Object value = result.block(Duration.ofSeconds(5));
assertNotNull(value);
assertTrue("Unexpected return value type: " + value.getClass(),
param.getParameterType().isAssignableFrom(value.getClass()));
return (T) value;
}
@SuppressWarnings("unchecked")
private <T> HttpEntity<T> resolveValueWithEmptyBody(ResolvableType type) {
ServerWebExchange exchange = new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
MethodParameter param = this.testMethod.resolveParam(type);
Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange);
HttpEntity<String> httpEntity = (HttpEntity<String>) result.block(Duration.ofSeconds(5));
assertEquals(this.request.getHeaders(), httpEntity.getHeaders());
return (HttpEntity<T>) httpEntity;
}
private DataBuffer dataBuffer(String body) {
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
return new DefaultDataBufferFactory().wrap(byteBuffer);
}
@SuppressWarnings("unused")
void handle(
String string,
Mono<String> monoString,
HttpEntity<String> httpEntity,
HttpEntity<Mono<String>> monoBody,
HttpEntity<Flux<String>> fluxBody,
HttpEntity<Single<String>> singleBody,
HttpEntity<io.reactivex.Single<String>> rxJava2SingleBody,
HttpEntity<Maybe<String>> rxJava2MaybeBody,
HttpEntity<Observable<String>> observableBody,
HttpEntity<io.reactivex.Observable<String>> rxJava2ObservableBody,
HttpEntity<Flowable<String>> flowableBody,
HttpEntity<CompletableFuture<String>> completableFutureBody,
RequestEntity<String> requestEntity) {}
}

View File

@@ -0,0 +1,171 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
/**
* Unit tests for {@link InitBinderBindingContext}.
* @author Rossen Stoyanchev
*/
public class InitBinderBindingContextTests {
private final ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
private final List<SyncHandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
private MockServerHttpRequest request;
@Before
public void setUp() throws Exception {
this.request = MockServerHttpRequest.get("/").build();
}
@Test
public void createBinder() throws Exception {
BindingContext context = createBindingContext("initBinder", WebDataBinder.class);
WebDataBinder dataBinder = context.createDataBinder(createExchange(), null, null);
assertNotNull(dataBinder.getDisallowedFields());
assertEquals("id", dataBinder.getDisallowedFields()[0]);
}
@Test
public void createBinderWithGlobalInitialization() throws Exception {
ConversionService conversionService = new DefaultFormattingConversionService();
bindingInitializer.setConversionService(conversionService);
BindingContext context = createBindingContext("initBinder", WebDataBinder.class);
WebDataBinder dataBinder = context.createDataBinder(createExchange(), null, null);
assertSame(conversionService, dataBinder.getConversionService());
}
@Test
public void createBinderWithAttrName() throws Exception {
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
WebDataBinder dataBinder = context.createDataBinder(createExchange(), null, "foo");
assertNotNull(dataBinder.getDisallowedFields());
assertEquals("id", dataBinder.getDisallowedFields()[0]);
}
@Test
public void createBinderWithAttrNameNoMatch() throws Exception {
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
WebDataBinder dataBinder = context.createDataBinder(createExchange(), null, "invalidName");
assertNull(dataBinder.getDisallowedFields());
}
@Test
public void createBinderNullAttrName() throws Exception {
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
WebDataBinder dataBinder = context.createDataBinder(createExchange(), null, null);
assertNull(dataBinder.getDisallowedFields());
}
@Test(expected = IllegalStateException.class)
public void returnValueNotExpected() throws Exception {
BindingContext context = createBindingContext("initBinderReturnValue", WebDataBinder.class);
context.createDataBinder(createExchange(), null, "invalidName");
}
@Test
public void createBinderTypeConversion() throws Exception {
this.request = MockServerHttpRequest.get("/path?requestParam=22").build();
this.argumentResolvers.add(new RequestParamMethodArgumentResolver(null, false));
BindingContext context = createBindingContext("initBinderTypeConversion", WebDataBinder.class, int.class);
WebDataBinder dataBinder = context.createDataBinder(createExchange(), null, "foo");
assertNotNull(dataBinder.getDisallowedFields());
assertEquals("requestParam-22", dataBinder.getDisallowedFields()[0]);
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
private BindingContext createBindingContext(String methodName, Class<?>... parameterTypes)
throws Exception {
Object handler = new InitBinderHandler();
Method method = handler.getClass().getMethod(methodName, parameterTypes);
SyncInvocableHandlerMethod handlerMethod = new SyncInvocableHandlerMethod(handler, method);
handlerMethod.setArgumentResolvers(new ArrayList<>(this.argumentResolvers));
handlerMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer());
return new InitBinderBindingContext(
this.bindingInitializer, Collections.singletonList(handlerMethod));
}
private static class InitBinderHandler {
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
}
@InitBinder(value="foo")
public void initBinderWithAttributeName(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
}
@InitBinder
public String initBinderReturnValue(WebDataBinder dataBinder) {
return "invalid";
}
@InitBinder
public void initBinderTypeConversion(WebDataBinder dataBinder, @RequestParam int requestParam) {
dataBinder.setDisallowedFields("requestParam-" + requestParam);
}
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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.web.reactive.result.method.annotation;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonView;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.EnableWebReactive;
/**
* @author Sebastien Deleuze
*/
public class JacksonHintsIntegrationTests extends AbstractRequestMappingIntegrationTests {
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
return wac;
}
@Test
public void jsonViewResponse() throws Exception {
String expected = "{\"withView1\":\"with\"}";
assertEquals(expected, performGet("/response/raw", MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Test
public void jsonViewWithMonoResponse() throws Exception {
String expected = "{\"withView1\":\"with\"}";
assertEquals(expected, performGet("/response/mono", MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Test
public void jsonViewWithFluxResponse() throws Exception {
String expected = "[{\"withView1\":\"with\"},{\"withView1\":\"with\"}]";
assertEquals(expected, performGet("/response/flux", MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Test
public void jsonViewWithRequest() throws Exception {
String expected = "{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}";
assertEquals(expected, performPost("/request/raw", MediaType.APPLICATION_JSON,
new JacksonViewBean("with", "with", "without"), MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Test
public void jsonViewWithMonoRequest() throws Exception {
String expected = "{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}";
assertEquals(expected, performPost("/request/mono", MediaType.APPLICATION_JSON,
new JacksonViewBean("with", "with", "without"), MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Test
public void jsonViewWithFluxRequest() throws Exception {
String expected = "[{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}," +
"{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}]";
List<JacksonViewBean> beans = Arrays.asList(new JacksonViewBean("with", "with", "without"), new JacksonViewBean("with", "with", "without"));
assertEquals(expected, performPost("/request/flux", MediaType.APPLICATION_JSON, beans,
MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Configuration
@ComponentScan(resourcePattern = "**/JacksonHintsIntegrationTests*.class")
@EnableWebReactive
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig {
}
@RestController
@SuppressWarnings("unused")
private static class JsonViewRestController {
@GetMapping("/response/raw")
@JsonView(MyJacksonView1.class)
public JacksonViewBean rawResponse() {
return new JacksonViewBean("with", "with", "without");
}
@GetMapping("/response/mono")
@JsonView(MyJacksonView1.class)
public Mono<JacksonViewBean> monoResponse() {
return Mono.just(new JacksonViewBean("with", "with", "without"));
}
@GetMapping("/response/flux")
@JsonView(MyJacksonView1.class)
public Flux<JacksonViewBean> fluxResponse() {
return Flux.just(new JacksonViewBean("with", "with", "without"), new JacksonViewBean("with", "with", "without"));
}
@PostMapping("/request/raw")
public JacksonViewBean rawRequest(@JsonView(MyJacksonView1.class) @RequestBody JacksonViewBean bean) {
return bean;
}
@PostMapping("/request/mono")
public Mono<JacksonViewBean> monoRequest(@JsonView(MyJacksonView1.class) @RequestBody Mono<JacksonViewBean> mono) {
return mono;
}
@PostMapping("/request/flux")
public Flux<JacksonViewBean> fluxRequest(@JsonView(MyJacksonView1.class) @RequestBody Flux<JacksonViewBean> flux) {
return flux;
}
}
private interface MyJacksonView1 {}
private interface MyJacksonView2 {}
@SuppressWarnings("unused")
private static class JacksonViewBean {
@JsonView(MyJacksonView1.class)
private String withView1;
@JsonView(MyJacksonView2.class)
private String withView2;
private String withoutView;
public JacksonViewBean() {
}
public JacksonViewBean(String withView1, String withView2, String withoutView) {
this.withView1 = withView1;
this.withView2 = withView2;
this.withoutView = withoutView;
}
public String getWithView1() {
return withView1;
}
public void setWithView1(String withView1) {
this.withView1 = withView1;
}
public String getWithView2() {
return withView2;
}
public void setWithView2(String withView2) {
this.withView2 = withView2;
}
public String getWithoutView() {
return withoutView;
}
public void setWithoutView(String withoutView) {
this.withoutView = withoutView;
}
}
}

View File

@@ -0,0 +1,468 @@
/*
* 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.web.reactive.result.method.annotation;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import javax.xml.bind.annotation.XmlRootElement;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import rx.Observable;
import rx.Single;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Decoder;
import org.springframework.http.MediaType;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClass;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
/**
* Unit tests for {@link AbstractMessageReaderArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class MessageReaderArgumentResolverTests {
private AbstractMessageReaderArgumentResolver resolver = resolver(new Jackson2JsonDecoder());
private MockServerHttpRequest request;
private BindingContext bindingContext;
private ResolvableMethod testMethod = ResolvableMethod.onClass(this.getClass()).name("handle");
@Before
public void setUp() throws Exception {
this.request = request().build();
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setValidator(new TestBeanValidator());
this.bindingContext = new BindingContext(initializer);
}
@Test
public void missingContentType() throws Exception {
this.request = request().body("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}");
ResolvableType type = forClassWithGenerics(Mono.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
Mono<Object> result = this.resolver.readBody(param, true, this.bindingContext, exchange());
StepVerifier.create(result).expectError(UnsupportedMediaTypeStatusException.class).verify();
}
// More extensive "empty body" tests in RequestBody- and HttpEntityArgumentResolverTests
@Test @SuppressWarnings("unchecked") // SPR-9942
public void emptyBody() throws Exception {
this.request = request().header("Content-Type", "application/json").build();
ResolvableType type = forClassWithGenerics(Mono.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
Mono<TestBean> result = (Mono<TestBean>) this.resolver.readBody(
param, true, this.bindingContext, exchange()).block();
StepVerifier.create(result).expectError(ServerWebInputException.class).verify();
}
@Test
public void monoTestBean() throws Exception {
String body = "{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}";
ResolvableType type = forClassWithGenerics(Mono.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
Mono<Object> mono = resolveValue(param, body);
assertEquals(new TestBean("FOOFOO", "BARBAR"), mono.block());
}
@Test
public void fluxTestBean() throws Exception {
String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]";
ResolvableType type = forClassWithGenerics(Flux.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
Flux<TestBean> flux = resolveValue(param, body);
assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")),
flux.collectList().block());
}
@Test
public void singleTestBean() throws Exception {
String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}";
ResolvableType type = forClassWithGenerics(Single.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
Single<TestBean> single = resolveValue(param, body);
assertEquals(new TestBean("f1", "b1"), single.toBlocking().value());
}
@Test
public void rxJava2SingleTestBean() throws Exception {
String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}";
ResolvableType type = forClassWithGenerics(io.reactivex.Single.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
io.reactivex.Single<TestBean> single = resolveValue(param, body);
assertEquals(new TestBean("f1", "b1"), single.blockingGet());
}
@Test
public void rxJava2MaybeTestBean() throws Exception {
String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}";
ResolvableType type = forClassWithGenerics(Maybe.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
Maybe<TestBean> maybe = resolveValue(param, body);
assertEquals(new TestBean("f1", "b1"), maybe.blockingGet());
}
@Test
public void observableTestBean() throws Exception {
String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]";
ResolvableType type = forClassWithGenerics(Observable.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
Observable<?> observable = resolveValue(param, body);
assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")),
observable.toList().toBlocking().first());
}
@Test
public void rxJava2ObservableTestBean() throws Exception {
String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]";
ResolvableType type = forClassWithGenerics(io.reactivex.Observable.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
io.reactivex.Observable<?> observable = resolveValue(param, body);
assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")),
observable.toList().blockingGet());
}
@Test
public void flowableTestBean() throws Exception {
String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]";
ResolvableType type = forClassWithGenerics(Flowable.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
Flowable<?> flowable = resolveValue(param, body);
assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")),
flowable.toList().blockingGet());
}
@Test
public void futureTestBean() throws Exception {
String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}";
ResolvableType type = forClassWithGenerics(CompletableFuture.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
CompletableFuture<?> future = resolveValue(param, body);
assertEquals(new TestBean("f1", "b1"), future.get());
}
@Test
public void testBean() throws Exception {
String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}";
MethodParameter param = this.testMethod.resolveParam(forClass(TestBean.class));
TestBean value = resolveValue(param, body);
assertEquals(new TestBean("f1", "b1"), value);
}
@Test
public void map() throws Exception {
String body = "{\"bar\":\"b1\",\"foo\":\"f1\"}";
Map<String, String> map = new HashMap<>();
map.put("foo", "f1");
map.put("bar", "b1");
ResolvableType type = forClassWithGenerics(Map.class, String.class, String.class);
MethodParameter param = this.testMethod.resolveParam(type);
Map actual = resolveValue(param, body);
assertEquals(map, actual);
}
@Test
public void list() throws Exception {
String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]";
ResolvableType type = forClassWithGenerics(List.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
List<?> list = resolveValue(param, body);
assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")), list);
}
@Test
public void monoList() throws Exception {
String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]";
ResolvableType type = forClassWithGenerics(Mono.class, forClassWithGenerics(List.class, TestBean.class));
MethodParameter param = this.testMethod.resolveParam(type);
Mono<?> mono = resolveValue(param, body);
List<?> list = (List<?>) mono.block(Duration.ofSeconds(5));
assertEquals(Arrays.asList(new TestBean("f1", "b1"), new TestBean("f2", "b2")), list);
}
@Test
public void array() throws Exception {
String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]";
ResolvableType type = forClass(TestBean[].class);
MethodParameter param = this.testMethod.resolveParam(type);
TestBean[] value = resolveValue(param, body);
assertArrayEquals(new TestBean[] {new TestBean("f1", "b1"), new TestBean("f2", "b2")}, value);
}
@Test
@SuppressWarnings("unchecked")
public void validateMonoTestBean() throws Exception {
String body = "{\"bar\":\"b1\"}";
ResolvableType type = forClassWithGenerics(Mono.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
Mono<TestBean> mono = resolveValue(param, body);
StepVerifier.create(mono).expectNextCount(0).expectError(ServerWebInputException.class).verify();
}
@Test
@SuppressWarnings("unchecked")
public void validateFluxTestBean() throws Exception {
String body = "[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\"}]";
ResolvableType type = forClassWithGenerics(Flux.class, TestBean.class);
MethodParameter param = this.testMethod.resolveParam(type);
Flux<TestBean> flux = resolveValue(param, body);
StepVerifier.create(flux)
.expectNext(new TestBean("f1", "b1"))
.expectError(ServerWebInputException.class)
.verify();
}
@Test // SPR-9964
public void parameterizedMethodArgument() throws Exception {
Method method = AbstractParameterizedController.class.getMethod("handleDto", Identifiable.class);
HandlerMethod handlerMethod = new HandlerMethod(new ConcreteParameterizedController(), method);
MethodParameter methodParam = handlerMethod.getMethodParameters()[0];
SimpleBean simpleBean = resolveValue(methodParam, "{\"name\" : \"Jad\"}");
assertEquals("Jad", simpleBean.getName());
}
@SuppressWarnings("unchecked")
private <T> T resolveValue(MethodParameter param, String body) {
this.request = request().contentType(MediaType.APPLICATION_JSON).body(body);
Mono<Object> result = this.resolver.readBody(param, true, this.bindingContext, exchange());
Object value = result.block(Duration.ofSeconds(5));
assertNotNull(value);
assertTrue("Unexpected return value type: " + value,
param.getParameterType().isAssignableFrom(value.getClass()));
return (T) value;
}
private MockServerHttpRequest.BodyBuilder request() {
return MockServerHttpRequest.post("/path");
}
@NotNull
private DefaultServerWebExchange exchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
@SuppressWarnings("Convert2MethodRef")
private AbstractMessageReaderArgumentResolver resolver(Decoder<?>... decoders) {
List<HttpMessageReader<?>> readers = new ArrayList<>();
Arrays.asList(decoders).forEach(decoder -> readers.add(new DecoderHttpMessageReader<>(decoder)));
return new AbstractMessageReaderArgumentResolver(readers) {};
}
@SuppressWarnings("unused")
private void handle(
@Validated Mono<TestBean> monoTestBean,
@Validated Flux<TestBean> fluxTestBean,
Single<TestBean> singleTestBean,
io.reactivex.Single<TestBean> rxJava2SingleTestBean,
Maybe<TestBean> rxJava2MaybeTestBean,
Observable<TestBean> observableTestBean,
io.reactivex.Observable<TestBean> rxJava2ObservableTestBean,
Flowable<TestBean> flowableTestBean,
CompletableFuture<TestBean> futureTestBean,
TestBean testBean,
Map<String, String> map,
List<TestBean> list,
Mono<List<TestBean>> monoList,
Set<TestBean> set,
TestBean[] array) {
}
@XmlRootElement
private static class TestBean {
private String foo;
private String bar;
@SuppressWarnings("unused")
public TestBean() {
}
TestBean(String foo, String bar) {
this.foo = foo;
this.bar = bar;
}
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof TestBean) {
TestBean other = (TestBean) o;
return this.foo.equals(other.foo) && this.bar.equals(other.bar);
}
return false;
}
@Override
public int hashCode() {
return 31 * foo.hashCode() + bar.hashCode();
}
@Override
public String toString() {
return "TestBean[foo='" + this.foo + "\'" + ", bar='" + this.bar + "\']";
}
}
private static class TestBeanValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return clazz.equals(TestBean.class);
}
@Override
public void validate(Object target, Errors errors) {
TestBean testBean = (TestBean) target;
if (testBean.getFoo() == null) {
errors.rejectValue("foo", "nullValue");
}
}
}
private static abstract class AbstractParameterizedController<DTO extends Identifiable> {
@SuppressWarnings("unused")
public void handleDto(DTO dto) {}
}
private static class ConcreteParameterizedController extends AbstractParameterizedController<SimpleBean> {
}
private interface Identifiable extends Serializable {
Long getId();
void setId(Long id);
}
@SuppressWarnings({"serial"})
private static class SimpleBean implements Identifiable {
private Long id;
private String name;
@Override
public Long getId() {
return id;
}
@Override
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -0,0 +1,301 @@
/*
* 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.web.reactive.result.method.annotation;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.reactivex.Flowable;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import rx.Completable;
import rx.Observable;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
import org.springframework.http.codec.EncoderHttpMessageWriter;
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.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.web.reactive.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
/**
* Unit tests for {@link AbstractMessageWriterResultHandler}.
* @author Rossen Stoyanchev
*/
public class MessageWriterResultHandlerTests {
private AbstractMessageWriterResultHandler resultHandler;
private MockServerHttpResponse response = new MockServerHttpResponse();
private ServerWebExchange exchange;
@Before
public void setUp() throws Exception {
this.resultHandler = createResultHandler();
ServerHttpRequest request = MockServerHttpRequest.get("/path").build();
this.exchange = new DefaultServerWebExchange(request, this.response);
}
@Test // SPR-12894
public void useDefaultContentType() throws Exception {
Resource body = new ClassPathResource("logo.png", getClass());
ResolvableType type = ResolvableType.forType(Resource.class);
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertEquals("image/x-png", this.response.getHeaders().getFirst("Content-Type"));
}
@Test // SPR-13631
public void useDefaultCharset() throws Exception {
this.exchange.getAttributes().put(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE,
Collections.singleton(APPLICATION_JSON));
String body = "foo";
ResolvableType type = ResolvableType.forType(String.class);
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
}
@Test
public void voidReturnType() throws Exception {
testVoidReturnType(null, ResolvableType.forType(void.class));
testVoidReturnType(Mono.empty(), ResolvableType.forClassWithGenerics(Mono.class, Void.class));
testVoidReturnType(Completable.complete(), ResolvableType.forClass(Completable.class));
testVoidReturnType(io.reactivex.Completable.complete(), ResolvableType.forClass(io.reactivex.Completable.class));
testVoidReturnType(Flux.empty(), ResolvableType.forClassWithGenerics(Flux.class, Void.class));
testVoidReturnType(Observable.empty(), ResolvableType.forClassWithGenerics(Observable.class, Void.class));
testVoidReturnType(io.reactivex.Observable.empty(), ResolvableType.forClassWithGenerics(io.reactivex.Observable.class, Void.class));
testVoidReturnType(Flowable.empty(), ResolvableType.forClassWithGenerics(Flowable.class, Void.class));
}
private void testVoidReturnType(Object body, ResolvableType type) {
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertNull(this.response.getHeaders().get("Content-Type"));
assertNull(this.response.getBody());
}
@Test // SPR-13135
public void unsupportedReturnType() throws Exception {
ByteArrayOutputStream body = new ByteArrayOutputStream();
ResolvableType type = ResolvableType.forType(OutputStream.class);
HttpMessageWriter<?> writer = new EncoderHttpMessageWriter<>(new ByteBufferEncoder());
Mono<Void> mono = createResultHandler(writer).writeBody(body, returnType(type), this.exchange);
StepVerifier.create(mono).expectError(IllegalStateException.class).verify();
}
@Test // SPR-12811
public void jacksonTypeOfListElement() throws Exception {
List<ParentClass> body = Arrays.asList(new Foo("foo"), new Bar("bar"));
ResolvableType type = ResolvableType.forClassWithGenerics(List.class, ParentClass.class);
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
assertResponseBody("[{\"type\":\"foo\",\"parentProperty\":\"foo\"}," +
"{\"type\":\"bar\",\"parentProperty\":\"bar\"}]");
}
@Test // SPR-13318
public void jacksonTypeWithSubType() throws Exception {
SimpleBean body = new SimpleBean(123L, "foo");
ResolvableType type = ResolvableType.forClass(Identifiable.class);
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
assertResponseBody("{\"id\":123,\"name\":\"foo\"}");
}
@Test // SPR-13318
public void jacksonTypeWithSubTypeOfListElement() throws Exception {
List<SimpleBean> body = Arrays.asList(new SimpleBean(123L, "foo"), new SimpleBean(456L, "bar"));
ResolvableType type = ResolvableType.forClassWithGenerics(List.class, Identifiable.class);
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
assertEquals(APPLICATION_JSON_UTF8, this.response.getHeaders().getContentType());
assertResponseBody("[{\"id\":123,\"name\":\"foo\"},{\"id\":456,\"name\":\"bar\"}]");
}
private MethodParameter returnType(ResolvableType bodyType) {
return ResolvableMethod.onClass(TestController.class).returning(bodyType).resolveReturnType();
}
private AbstractMessageWriterResultHandler createResultHandler(HttpMessageWriter<?>... writers) {
List<HttpMessageWriter<?>> writerList;
if (ObjectUtils.isEmpty(writers)) {
writerList = new ArrayList<>();
writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
writerList.add(new ResourceHttpMessageWriter());
writerList.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));
}
else {
writerList = Arrays.asList(writers);
}
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
return new AbstractMessageWriterResultHandler(writerList, resolver) {};
}
private void assertResponseBody(String responseBody) {
StepVerifier.create(this.response.getBody())
.consumeNextWith(buf -> assertEquals(responseBody,
DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8)))
.expectComplete()
.verify();
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@SuppressWarnings("unused")
private static class ParentClass {
private String parentProperty;
public ParentClass() {
}
ParentClass(String parentProperty) {
this.parentProperty = parentProperty;
}
public String getParentProperty() {
return parentProperty;
}
public void setParentProperty(String parentProperty) {
this.parentProperty = parentProperty;
}
}
@JsonTypeName("foo")
private static class Foo extends ParentClass {
public Foo(String parentProperty) {
super(parentProperty);
}
}
@JsonTypeName("bar")
private static class Bar extends ParentClass {
Bar(String parentProperty) {
super(parentProperty);
}
}
private interface Identifiable extends Serializable {
@SuppressWarnings("unused")
Long getId();
}
@SuppressWarnings({ "serial" })
private static class SimpleBean implements Identifiable {
private Long id;
private String name;
SimpleBean(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
@SuppressWarnings("unused")
private static class TestController {
Resource resource() { return null; }
String string() { return null; }
void voidReturn() { }
Mono<Void> monoVoid() { return null; }
Completable completable() { return null; }
io.reactivex.Completable rxJava2Completable() { return null; }
Flux<Void> fluxVoid() { return null; }
Observable<Void> observableVoid() { return null; }
io.reactivex.Observable<Void> rxJava2ObservableVoid() { return null; }
Flowable<Void> flowableVoid() { return null; }
OutputStream outputStream() { return null; }
List<ParentClass> listParentClass() { return null; }
Identifiable identifiable() { return null; }
List<Identifiable> listIdentifiable() { return null; }
}
}

View File

@@ -0,0 +1,324 @@
/*
* 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.result.method.annotation;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.function.Function;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import rx.RxReactiveStreams;
import rx.Single;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.*;
import static org.springframework.core.ResolvableType.*;
/**
* Unit tests for {@link ModelAttributeMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class ModelAttributeMethodArgumentResolverTests {
private BindingContext bindContext;
private ResolvableMethod testMethod = ResolvableMethod.onClass(this.getClass()).name("handle");
@Before
public void setUp() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setValidator(validator);
this.bindContext = new BindingContext(initializer);
}
@Test
public void supports() throws Exception {
ModelAttributeMethodArgumentResolver resolver =
new ModelAttributeMethodArgumentResolver(new ReactiveAdapterRegistry(), false);
ResolvableType type = forClass(Foo.class);
assertTrue(resolver.supportsParameter(parameter(type)));
type = forClassWithGenerics(Mono.class, Foo.class);
assertTrue(resolver.supportsParameter(parameter(type)));
type = forClass(Foo.class);
assertFalse(resolver.supportsParameter(parameterNotAnnotated(type)));
type = forClassWithGenerics(Mono.class, Foo.class);
assertFalse(resolver.supportsParameter(parameterNotAnnotated(type)));
}
@Test
public void supportsWithDefaultResolution() throws Exception {
ModelAttributeMethodArgumentResolver resolver =
new ModelAttributeMethodArgumentResolver(new ReactiveAdapterRegistry(), true);
ResolvableType type = forClass(Foo.class);
assertTrue(resolver.supportsParameter(parameterNotAnnotated(type)));
type = forClassWithGenerics(Mono.class, Foo.class);
assertTrue(resolver.supportsParameter(parameterNotAnnotated(type)));
type = forClass(String.class);
assertFalse(resolver.supportsParameter(parameterNotAnnotated(type)));
type = forClassWithGenerics(Mono.class, String.class);
assertFalse(resolver.supportsParameter(parameterNotAnnotated(type)));
}
@Test
public void createAndBind() throws Exception {
testBindFoo(forClass(Foo.class), value -> {
assertEquals(Foo.class, value.getClass());
return (Foo) value;
});
}
@Test
public void createAndBindToMono() throws Exception {
testBindFoo(forClassWithGenerics(Mono.class, Foo.class), mono -> {
assertTrue(mono.getClass().getName(), mono instanceof Mono);
Object value = ((Mono<?>) mono).blockMillis(5000);
assertEquals(Foo.class, value.getClass());
return (Foo) value;
});
}
@Test
public void createAndBindToSingle() throws Exception {
testBindFoo(forClassWithGenerics(Single.class, Foo.class), single -> {
assertTrue(single.getClass().getName(), single instanceof Single);
Object value = ((Single<?>) single).toBlocking().value();
assertEquals(Foo.class, value.getClass());
return (Foo) value;
});
}
@Test
public void bindExisting() throws Exception {
Foo foo = new Foo();
foo.setName("Jim");
this.bindContext.getModel().addAttribute(foo);
testBindFoo(forClass(Foo.class), value -> {
assertEquals(Foo.class, value.getClass());
return (Foo) value;
});
assertSame(foo, this.bindContext.getModel().asMap().get("foo"));
}
@Test
public void bindExistingMono() throws Exception {
Foo foo = new Foo();
foo.setName("Jim");
this.bindContext.getModel().addAttribute("foo", Mono.just(foo));
testBindFoo(forClass(Foo.class), value -> {
assertEquals(Foo.class, value.getClass());
return (Foo) value;
});
assertSame(foo, this.bindContext.getModel().asMap().get("foo"));
}
@Test
public void bindExistingSingle() throws Exception {
Foo foo = new Foo();
foo.setName("Jim");
this.bindContext.getModel().addAttribute("foo", Single.just(foo));
testBindFoo(forClass(Foo.class), value -> {
assertEquals(Foo.class, value.getClass());
return (Foo) value;
});
assertSame(foo, this.bindContext.getModel().asMap().get("foo"));
}
@Test
public void bindExistingMonoToMono() throws Exception {
Foo foo = new Foo();
foo.setName("Jim");
this.bindContext.getModel().addAttribute("foo", Mono.just(foo));
testBindFoo(forClassWithGenerics(Mono.class, Foo.class), mono -> {
assertTrue(mono.getClass().getName(), mono instanceof Mono);
Object value = ((Mono<?>) mono).blockMillis(5000);
assertEquals(Foo.class, value.getClass());
return (Foo) value;
});
}
@Test
public void validationError() throws Exception {
testValidationError(forClass(Foo.class), resolvedArgumentMono -> resolvedArgumentMono);
}
@Test
@SuppressWarnings("unchecked")
public void validationErrorToMono() throws Exception {
testValidationError(forClassWithGenerics(Mono.class, Foo.class),
resolvedArgumentMono -> {
Object value = resolvedArgumentMono.blockMillis(5000);
assertNotNull(value);
assertTrue(value instanceof Mono);
return (Mono<?>) value;
});
}
@Test
@SuppressWarnings("unchecked")
public void validationErrorToSingle() throws Exception {
testValidationError(forClassWithGenerics(Single.class, Foo.class),
resolvedArgumentMono -> {
Object value = resolvedArgumentMono.blockMillis(5000);
assertNotNull(value);
assertTrue(value instanceof Single);
return Mono.from(RxReactiveStreams.toPublisher((Single) value));
});
}
private void testBindFoo(ResolvableType type, Function<Object, Foo> valueExtractor) throws Exception {
Object value = createResolver()
.resolveArgument(parameter(type), this.bindContext, exchange("name=Robert&age=25"))
.blockMillis(0);
Foo foo = valueExtractor.apply(value);
assertEquals("Robert", foo.getName());
String key = "foo";
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key;
Map<String, Object> map = bindContext.getModel().asMap();
assertEquals(map.toString(), 2, map.size());
assertSame(foo, map.get(key));
assertNotNull(map.get(bindingResultKey));
assertTrue(map.get(bindingResultKey) instanceof BindingResult);
}
private void testValidationError(ResolvableType type, Function<Mono<?>, Mono<?>> valueMonoExtractor)
throws URISyntaxException {
ServerWebExchange exchange = exchange("age=invalid");
Mono<?> mono = createResolver().resolveArgument(parameter(type), this.bindContext, exchange);
mono = valueMonoExtractor.apply(mono);
StepVerifier.create(mono)
.consumeErrorWith(ex -> {
assertTrue(ex instanceof WebExchangeBindException);
WebExchangeBindException bindException = (WebExchangeBindException) ex;
assertEquals(1, bindException.getErrorCount());
assertTrue(bindException.hasFieldErrors("age"));
})
.verify();
}
private ModelAttributeMethodArgumentResolver createResolver() {
return new ModelAttributeMethodArgumentResolver(new ReactiveAdapterRegistry());
}
private MethodParameter parameter(ResolvableType type) {
return this.testMethod.resolveParam(type,
parameter -> parameter.hasParameterAnnotation(ModelAttribute.class));
}
private MethodParameter parameterNotAnnotated(ResolvableType type) {
return this.testMethod.resolveParam(type,
parameter -> !parameter.hasParameterAnnotations());
}
private ServerWebExchange exchange(String formData) throws URISyntaxException {
MediaType mediaType = MediaType.APPLICATION_FORM_URLENCODED;
MockServerHttpRequest request = MockServerHttpRequest.post("/").contentType(mediaType).body(formData);
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
@SuppressWarnings("unused")
void handle(
@ModelAttribute @Validated Foo foo,
@ModelAttribute @Validated Mono<Foo> mono,
@ModelAttribute @Validated Single<Foo> single,
Foo fooNotAnnotated,
String stringNotAnnotated,
Mono<Foo> monoNotAnnotated,
Mono<String> monoStringNotAnnotated) {
}
private static class Foo {
private String name;
private int age;
public Foo() {
}
public Foo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link PathVariableMapMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class PathVariableMapMethodArgumentResolverTests {
private PathVariableMapMethodArgumentResolver resolver;
private ServerWebExchange exchange;
private MethodParameter paramMap;
private MethodParameter paramNamedMap;
private MethodParameter paramMapNoAnnot;
@Before
public void setUp() throws Exception {
this.resolver = new PathVariableMapMethodArgumentResolver();
ServerHttpRequest request = MockServerHttpRequest.get("/").build();
this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
Method method = getClass().getMethod("handle", Map.class, Map.class, Map.class);
this.paramMap = new MethodParameter(method, 0);
this.paramNamedMap = new MethodParameter(method, 1);
this.paramMapNoAnnot = new MethodParameter(method, 2);
}
@Test
public void supportsParameter() {
assertTrue(resolver.supportsParameter(paramMap));
assertFalse(resolver.supportsParameter(paramNamedMap));
assertFalse(resolver.supportsParameter(paramMapNoAnnot));
}
@Test
public void resolveArgument() throws Exception {
Map<String, String> uriTemplateVars = new HashMap<>();
uriTemplateVars.put("name1", "value1");
uriTemplateVars.put("name2", "value2");
this.exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
Mono<Object> mono = this.resolver.resolveArgument(this.paramMap, new BindingContext(), this.exchange);
Object result = mono.block();
assertEquals(uriTemplateVars, result);
}
@Test
public void resolveArgumentNoUriVars() throws Exception {
Mono<Object> mono = this.resolver.resolveArgument(this.paramMap, new BindingContext(), this.exchange);
Object result = mono.block();
assertEquals(Collections.emptyMap(), result);
}
@SuppressWarnings("unused")
public void handle(
@PathVariable Map<String, String> map,
@PathVariable(value = "name") Map<String, String> namedMap,
Map<String, String> mapWithoutAnnotat) {
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.server.ServerErrorException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link PathVariableMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
*/
public class PathVariableMethodArgumentResolverTests {
private PathVariableMethodArgumentResolver resolver;
private ServerWebExchange exchange;
private MethodParameter paramNamedString;
private MethodParameter paramString;
private MethodParameter paramNotRequired;
private MethodParameter paramOptional;
@Before
public void setUp() throws Exception {
this.resolver = new PathVariableMethodArgumentResolver(null);
ServerHttpRequest request = MockServerHttpRequest.get("/").build();
this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
Method method = ReflectionUtils.findMethod(getClass(), "handle", (Class<?>[]) null);
paramNamedString = new SynthesizingMethodParameter(method, 0);
paramString = new SynthesizingMethodParameter(method, 1);
paramNotRequired = new SynthesizingMethodParameter(method, 2);
paramOptional = new SynthesizingMethodParameter(method, 3);
}
@Test
public void supportsParameter() {
assertTrue(this.resolver.supportsParameter(this.paramNamedString));
assertFalse(this.resolver.supportsParameter(this.paramString));
}
@Test
public void resolveArgument() throws Exception {
Map<String, String> uriTemplateVars = new HashMap<>();
uriTemplateVars.put("name", "value");
this.exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
BindingContext bindingContext = new BindingContext();
Mono<Object> mono = this.resolver.resolveArgument(this.paramNamedString, bindingContext, this.exchange);
Object result = mono.block();
assertEquals("value", result);
}
@Test
public void resolveArgumentNotRequired() throws Exception {
Map<String, String> uriTemplateVars = new HashMap<>();
uriTemplateVars.put("name", "value");
this.exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
BindingContext bindingContext = new BindingContext();
Mono<Object> mono = this.resolver.resolveArgument(this.paramNotRequired, bindingContext, this.exchange);
Object result = mono.block();
assertEquals("value", result);
}
@Test
public void resolveArgumentWrappedAsOptional() throws Exception {
Map<String, String> uriTemplateVars = new HashMap<>();
uriTemplateVars.put("name", "value");
this.exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(new DefaultFormattingConversionService());
BindingContext bindingContext = new BindingContext(initializer);
Mono<Object> mono = this.resolver.resolveArgument(this.paramOptional, bindingContext, this.exchange);
Object result = mono.block();
assertEquals(Optional.of("value"), result);
}
@Test
public void handleMissingValue() throws Exception {
BindingContext bindingContext = new BindingContext();
Mono<Object> mono = this.resolver.resolveArgument(this.paramNamedString, bindingContext, this.exchange);
StepVerifier.create(mono)
.expectNextCount(0)
.expectError(ServerErrorException.class)
.verify();
}
@Test
public void nullIfNotRequired() throws Exception {
BindingContext bindingContext = new BindingContext();
Mono<Object> mono = this.resolver.resolveArgument(this.paramNotRequired, bindingContext, this.exchange);
StepVerifier.create(mono)
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void wrapEmptyWithOptional() throws Exception {
BindingContext bindingContext = new BindingContext();
Mono<Object> mono = this.resolver.resolveArgument(this.paramOptional, bindingContext, this.exchange);
StepVerifier.create(mono)
.consumeNextWith(value -> {
assertTrue(value instanceof Optional);
assertFalse(((Optional) value).isPresent());
})
.expectComplete()
.verify();
}
@SuppressWarnings("unused")
public void handle(@PathVariable(value = "name") String param1, String param2,
@PathVariable(name = "name", required = false) String param3,
@PathVariable("name") Optional<String> param4) {
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link RequestAttributeMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class RequestAttributeMethodArgumentResolverTests {
private RequestAttributeMethodArgumentResolver resolver;
private ServerWebExchange exchange;
private Method handleMethod;
@Before
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
public void setUp() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();
this.resolver = new RequestAttributeMethodArgumentResolver(context.getBeanFactory());
ServerHttpRequest request = MockServerHttpRequest.get("/").build();
this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
this.handleMethod = ReflectionUtils.findMethod(getClass(), "handleWithRequestAttribute", (Class<?>[]) null);
}
@Test
public void supportsParameter() throws Exception {
assertTrue(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 0)));
assertFalse(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 4)));
}
@Test
public void resolve() throws Exception {
MethodParameter param = initMethodParameter(0);
Mono<Object> mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
StepVerifier.create(mono)
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
Foo foo = new Foo();
this.exchange.getAttributes().put("foo", foo);
mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
assertSame(foo, mono.block());
}
@Test
public void resolveWithName() throws Exception {
MethodParameter param = initMethodParameter(1);
Foo foo = new Foo();
this.exchange.getAttributes().put("specialFoo", foo);
Mono<Object> mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
assertSame(foo, mono.block());
}
@Test
public void resolveNotRequired() throws Exception {
MethodParameter param = initMethodParameter(2);
Mono<Object> mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
assertNull(mono.block());
Foo foo = new Foo();
this.exchange.getAttributes().put("foo", foo);
mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
assertSame(foo, mono.block());
}
@Test
public void resolveOptional() throws Exception {
MethodParameter param = initMethodParameter(3);
Mono<Object> mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
assertNotNull(mono.block());
assertEquals(Optional.class, mono.block().getClass());
assertFalse(((Optional) mono.block()).isPresent());
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(new DefaultFormattingConversionService());
BindingContext bindingContext = new BindingContext(initializer);
Foo foo = new Foo();
this.exchange.getAttributes().put("foo", foo);
mono = this.resolver.resolveArgument(param, bindingContext, this.exchange);
assertNotNull(mono.block());
assertEquals(Optional.class, mono.block().getClass());
Optional optional = (Optional) mono.block();
assertTrue(optional.isPresent());
assertSame(foo, optional.get());
}
private MethodParameter initMethodParameter(int parameterIndex) {
MethodParameter param = new SynthesizingMethodParameter(this.handleMethod, parameterIndex);
param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
GenericTypeResolver.resolveParameterType(param, this.resolver.getClass());
return param;
}
@SuppressWarnings({"unused", "OptionalUsedAsFieldOrParameterType"})
private void handleWithRequestAttribute(
@RequestAttribute Foo foo,
@RequestAttribute("specialFoo") Foo namedFoo,
@RequestAttribute(name="foo", required = false) Foo notRequiredFoo,
@RequestAttribute(name="foo") Optional<Foo> optionalFoo,
String notSupported) {
}
private static class Foo {
}
}

View File

@@ -0,0 +1,278 @@
/*
* 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.web.reactive.result.method.annotation;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import io.reactivex.Maybe;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import rx.Observable;
import rx.RxReactiveStreams;
import rx.Single;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.StringDecoder;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClass;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
/**
* Unit tests for {@link RequestBodyArgumentResolver}. When adding a test also
* consider whether the logic under test is in a parent class, then see:
* {@link MessageReaderArgumentResolverTests}.
*
* @author Rossen Stoyanchev
*/
public class RequestBodyArgumentResolverTests {
private RequestBodyArgumentResolver resolver = resolver();
private ResolvableMethod testMethod = ResolvableMethod.onClass(this.getClass()).name("handle");
@Before
public void setUp() throws Exception {
}
private RequestBodyArgumentResolver resolver() {
List<HttpMessageReader<?>> readers = new ArrayList<>();
readers.add(new DecoderHttpMessageReader<>(new StringDecoder()));
return new RequestBodyArgumentResolver(readers);
}
@Test
public void supports() throws Exception {
ResolvableType type = forClassWithGenerics(Mono.class, String.class);
MethodParameter param = this.testMethod.resolveParam(type, requestBody(true));
assertTrue(this.resolver.supportsParameter(param));
MethodParameter parameter = this.testMethod.resolveParam(p -> !p.hasParameterAnnotations());
assertFalse(this.resolver.supportsParameter(parameter));
}
@Test
public void stringBody() throws Exception {
String body = "line1";
ResolvableType type = forClass(String.class);
MethodParameter param = this.testMethod.resolveParam(type, requestBody(true));
String value = resolveValue(param, body);
assertEquals(body, value);
}
@Test(expected = ServerWebInputException.class)
public void emptyBodyWithString() throws Exception {
resolveValueWithEmptyBody(forClass(String.class), true);
}
@Test
public void emptyBodyWithStringNotRequired() throws Exception {
ResolvableType type = forClass(String.class);
String body = resolveValueWithEmptyBody(type, false);
assertNull(body);
}
@Test
@SuppressWarnings("unchecked")
public void emptyBodyWithMono() throws Exception {
ResolvableType type = forClassWithGenerics(Mono.class, String.class);
StepVerifier.create((Mono<Void>) resolveValueWithEmptyBody(type, true))
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
StepVerifier.create((Mono<Void>) resolveValueWithEmptyBody(type, false))
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
@SuppressWarnings("unchecked")
public void emptyBodyWithFlux() throws Exception {
ResolvableType type = forClassWithGenerics(Flux.class, String.class);
StepVerifier.create((Flux<Void>) resolveValueWithEmptyBody(type, true))
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
StepVerifier.create((Flux<Void>) resolveValueWithEmptyBody(type, false))
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithSingle() throws Exception {
ResolvableType type = forClassWithGenerics(Single.class, String.class);
Single<String> single = resolveValueWithEmptyBody(type, true);
StepVerifier.create(RxReactiveStreams.toPublisher(single))
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
single = resolveValueWithEmptyBody(type, false);
StepVerifier.create(RxReactiveStreams.toPublisher(single))
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
}
@Test
public void emptyBodyWithMaybe() throws Exception {
ResolvableType type = forClassWithGenerics(Maybe.class, String.class);
Maybe<String> maybe = resolveValueWithEmptyBody(type, true);
StepVerifier.create(maybe.toFlowable())
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
maybe = resolveValueWithEmptyBody(type, false);
StepVerifier.create(maybe.toFlowable())
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithObservable() throws Exception {
ResolvableType type = forClassWithGenerics(Observable.class, String.class);
Observable<String> observable = resolveValueWithEmptyBody(type, true);
StepVerifier.create(RxReactiveStreams.toPublisher(observable))
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
observable = resolveValueWithEmptyBody(type, false);
StepVerifier.create(RxReactiveStreams.toPublisher(observable))
.expectNextCount(0)
.expectComplete()
.verify();
}
@Test
public void emptyBodyWithCompletableFuture() throws Exception {
ResolvableType type = forClassWithGenerics(CompletableFuture.class, String.class);
CompletableFuture<String> future = resolveValueWithEmptyBody(type, true);
future.whenComplete((text, ex) -> {
assertNull(text);
assertNotNull(ex);
});
future = resolveValueWithEmptyBody(type, false);
future.whenComplete((text, ex) -> {
assertNotNull(text);
assertNull(ex);
});
}
@SuppressWarnings("unchecked")
private <T> T resolveValue(MethodParameter param, String body) {
MockServerHttpRequest request = MockServerHttpRequest.post("/path").body(body);
ServerWebExchange exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
Mono<Object> result = this.resolver.readBody(param, true, new BindingContext(), exchange);
Object value = result.block(Duration.ofSeconds(5));
assertNotNull(value);
assertTrue("Unexpected return value type: " + value,
param.getParameterType().isAssignableFrom(value.getClass()));
//no inspection unchecked
return (T) value;
}
@SuppressWarnings("unchecked")
private <T> T resolveValueWithEmptyBody(ResolvableType bodyType, boolean isRequired) {
MockServerHttpRequest request = MockServerHttpRequest.post("/path").build();
ServerWebExchange exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse());
MethodParameter param = this.testMethod.resolveParam(bodyType, requestBody(isRequired));
Mono<Object> result = this.resolver.resolveArgument(param, new BindingContext(), exchange);
Object value = result.block(Duration.ofSeconds(5));
if (value != null) {
assertTrue("Unexpected return value type: " + value,
param.getParameterType().isAssignableFrom(value.getClass()));
}
//no inspection unchecked
return (T) value;
}
private Predicate<MethodParameter> requestBody(boolean required) {
return p -> {
RequestBody annotation = p.getParameterAnnotation(RequestBody.class);
return annotation != null && annotation.required() == required;
};
}
@SuppressWarnings("unused")
void handle(
@RequestBody String string,
@RequestBody Mono<String> mono,
@RequestBody Flux<String> flux,
@RequestBody Single<String> single,
@RequestBody io.reactivex.Single<String> rxJava2Single,
@RequestBody Maybe<String> rxJava2Maybe,
@RequestBody Observable<String> obs,
@RequestBody io.reactivex.Observable<String> rxjava2Obs,
@RequestBody CompletableFuture<String> future,
@RequestBody(required = false) String stringNotRequired,
@RequestBody(required = false) Mono<String> monoNotRequired,
@RequestBody(required = false) Flux<String> fluxNotRequired,
@RequestBody(required = false) Single<String> singleNotRequired,
@RequestBody(required = false) io.reactivex.Single<String> rxJava2SingleNotRequired,
@RequestBody(required = false) Maybe<String> rxJava2MaybeNotRequired,
@RequestBody(required = false) Observable<String> obsNotRequired,
@RequestBody(required = false) io.reactivex.Observable<String> rxjava2ObsNotRequired,
@RequestBody(required = false) CompletableFuture<String> futureNotRequired,
String notAnnotated) {}
}

View File

@@ -0,0 +1,146 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link RequestHeaderMapMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class RequestHeaderMapMethodArgumentResolverTests {
private RequestHeaderMapMethodArgumentResolver resolver;
private MethodParameter paramMap;
private MethodParameter paramMultiValueMap;
private MethodParameter paramHttpHeaders;
private MethodParameter paramUnsupported;
private ServerHttpRequest request;
@Before
public void setUp() throws Exception {
resolver = new RequestHeaderMapMethodArgumentResolver();
Method method = getClass().getMethod("params", Map.class, MultiValueMap.class, HttpHeaders.class, Map.class);
paramMap = new SynthesizingMethodParameter(method, 0);
paramMultiValueMap = new SynthesizingMethodParameter(method, 1);
paramHttpHeaders = new SynthesizingMethodParameter(method, 2);
paramUnsupported = new SynthesizingMethodParameter(method, 3);
this.request = MockServerHttpRequest.get("/").build();
}
@Test
public void supportsParameter() {
assertTrue("Map parameter not supported", resolver.supportsParameter(paramMap));
assertTrue("MultiValueMap parameter not supported", resolver.supportsParameter(paramMultiValueMap));
assertTrue("HttpHeaders parameter not supported", resolver.supportsParameter(paramHttpHeaders));
assertFalse("non-@RequestParam map supported", resolver.supportsParameter(paramUnsupported));
}
@Test
public void resolveMapArgument() throws Exception {
String name = "foo";
String value = "bar";
Map<String, String> expected = Collections.singletonMap(name, value);
this.request = MockServerHttpRequest.get("/").header(name, value).build();
Mono<Object> mono = this.resolver.resolveArgument(paramMap, null, createExchange());
Object result = mono.block();
assertTrue(result instanceof Map);
assertEquals("Invalid result", expected, result);
}
@Test
public void resolveMultiValueMapArgument() throws Exception {
String name = "foo";
String value1 = "bar";
String value2 = "baz";
this.request = MockServerHttpRequest.get("/").header(name, value1, value2).build();
MultiValueMap<String, String> expected = new LinkedMultiValueMap<>(1);
expected.add(name, value1);
expected.add(name, value2);
Mono<Object> mono = this.resolver.resolveArgument(paramMultiValueMap, null, createExchange());
Object result = mono.block();
assertTrue(result instanceof MultiValueMap);
assertEquals("Invalid result", expected, result);
}
@Test
public void resolveHttpHeadersArgument() throws Exception {
String name = "foo";
String value1 = "bar";
String value2 = "baz";
this.request = MockServerHttpRequest.get("/").header(name, value1, value2).build();
HttpHeaders expected = new HttpHeaders();
expected.add(name, value1);
expected.add(name, value2);
Mono<Object> mono = this.resolver.resolveArgument(paramHttpHeaders, null, createExchange());
Object result = mono.block();
assertTrue(result instanceof HttpHeaders);
assertEquals("Invalid result", expected, result);
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, new MockServerHttpResponse());
}
@SuppressWarnings("unused")
public void params(@RequestHeader Map<?, ?> param1,
@RequestHeader MultiValueMap<?, ?> param2,
@RequestHeader HttpHeaders param3,
Map<?,?> unsupported) {
}
}

View File

@@ -0,0 +1,248 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link RequestHeaderMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class RequestHeaderMethodArgumentResolverTests {
private RequestHeaderMethodArgumentResolver resolver;
private MethodParameter paramNamedDefaultValueStringHeader;
private MethodParameter paramNamedValueStringArray;
private MethodParameter paramSystemProperty;
private MethodParameter paramResolvedNameWithExpression;
private MethodParameter paramResolvedNameWithPlaceholder;
private MethodParameter paramNamedValueMap;
private MethodParameter paramDate;
private MethodParameter paramInstant;
private ServerHttpRequest request;
private BindingContext bindingContext;
@Before
public void setUp() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();
this.resolver = new RequestHeaderMethodArgumentResolver(context.getBeanFactory());
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
Method method = ReflectionUtils.findMethod(getClass(), "params", (Class<?>[]) null);
this.paramNamedDefaultValueStringHeader = new SynthesizingMethodParameter(method, 0);
this.paramNamedValueStringArray = new SynthesizingMethodParameter(method, 1);
this.paramSystemProperty = new SynthesizingMethodParameter(method, 2);
this.paramResolvedNameWithExpression = new SynthesizingMethodParameter(method, 3);
this.paramResolvedNameWithPlaceholder = new SynthesizingMethodParameter(method, 4);
this.paramNamedValueMap = new SynthesizingMethodParameter(method, 5);
this.paramDate = new SynthesizingMethodParameter(method, 6);
this.paramInstant = new SynthesizingMethodParameter(method, 7);
this.request = MockServerHttpRequest.get("/").build();
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(new DefaultFormattingConversionService());
this.bindingContext = new BindingContext(initializer);
}
@Test
public void supportsParameter() {
assertTrue("String parameter not supported", resolver.supportsParameter(paramNamedDefaultValueStringHeader));
assertTrue("String array parameter not supported", resolver.supportsParameter(paramNamedValueStringArray));
assertFalse("non-@RequestParam parameter supported", resolver.supportsParameter(paramNamedValueMap));
}
@Test
public void resolveStringArgument() throws Exception {
String expected = "foo";
this.request = MockServerHttpRequest.get("/").header("name", expected).build();
Mono<Object> mono = this.resolver.resolveArgument(
this.paramNamedDefaultValueStringHeader, this.bindingContext, createExchange());
Object result = mono.block();
assertTrue(result instanceof String);
assertEquals(expected, result);
}
@Test
public void resolveStringArrayArgument() throws Exception {
this.request = MockServerHttpRequest.get("/").header("name", "foo", "bar").build();
Mono<Object> mono = this.resolver.resolveArgument(
this.paramNamedValueStringArray, this.bindingContext, createExchange());
Object result = mono.block();
assertTrue(result instanceof String[]);
assertArrayEquals(new String[] {"foo", "bar"}, (String[]) result);
}
@Test
public void resolveDefaultValue() throws Exception {
Mono<Object> mono = this.resolver.resolveArgument(
this.paramNamedDefaultValueStringHeader, this.bindingContext, createExchange());
Object result = mono.block();
assertTrue(result instanceof String);
assertEquals("bar", result);
}
@Test
public void resolveDefaultValueFromSystemProperty() throws Exception {
System.setProperty("systemProperty", "bar");
try {
Mono<Object> mono = this.resolver.resolveArgument(
this.paramSystemProperty, this.bindingContext, createExchange());
Object result = mono.block();
assertTrue(result instanceof String);
assertEquals("bar", result);
}
finally {
System.clearProperty("systemProperty");
}
}
@Test
public void resolveNameFromSystemPropertyThroughExpression() throws Exception {
String expected = "foo";
this.request = MockServerHttpRequest.get("/").header("bar", expected).build();
System.setProperty("systemProperty", "bar");
try {
Mono<Object> mono = this.resolver.resolveArgument(
this.paramResolvedNameWithExpression, this.bindingContext, createExchange());
Object result = mono.block();
assertTrue(result instanceof String);
assertEquals(expected, result);
}
finally {
System.clearProperty("systemProperty");
}
}
@Test
public void resolveNameFromSystemPropertyThroughPlaceholder() throws Exception {
String expected = "foo";
this.request = MockServerHttpRequest.get("/").header("bar", expected).build();
System.setProperty("systemProperty", "bar");
try {
Mono<Object> mono = this.resolver.resolveArgument(
this.paramResolvedNameWithPlaceholder, this.bindingContext, createExchange());
Object result = mono.block();
assertTrue(result instanceof String);
assertEquals(expected, result);
}
finally {
System.clearProperty("systemProperty");
}
}
@Test
public void notFound() throws Exception {
Mono<Object> mono = resolver.resolveArgument(
this.paramNamedValueStringArray, this.bindingContext, createExchange());
StepVerifier.create(mono)
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
}
@Test
@SuppressWarnings("deprecation")
public void dateConversion() throws Exception {
String rfc1123val = "Thu, 21 Apr 2016 17:11:08 +0100";
this.request = MockServerHttpRequest.get("/").header("name", rfc1123val).build();
Mono<Object> mono = this.resolver.resolveArgument(this.paramDate, this.bindingContext, createExchange());
Object result = mono.block();
assertTrue(result instanceof Date);
assertEquals(new Date(rfc1123val), result);
}
@Test
public void instantConversion() throws Exception {
String rfc1123val = "Thu, 21 Apr 2016 17:11:08 +0100";
this.request = MockServerHttpRequest.get("/").header("name", rfc1123val).build();
Mono<Object> mono = this.resolver.resolveArgument(this.paramInstant, this.bindingContext, createExchange());
Object result = mono.block();
assertTrue(result instanceof Instant);
assertEquals(Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(rfc1123val)), result);
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
@SuppressWarnings("unused")
public void params(
@RequestHeader(name = "name", defaultValue = "bar") String param1,
@RequestHeader("name") String[] param2,
@RequestHeader(name = "name", defaultValue="#{systemProperties.systemProperty}") String param3,
@RequestHeader("#{systemProperties.systemProperty}") String param4,
@RequestHeader("${systemProperty}") String param5,
@RequestHeader("name") Map<?, ?> unsupported,
@RequestHeader("name") Date dateParam,
@RequestHeader("name") Instant instantParam) {
}
}

View File

@@ -0,0 +1,161 @@
/*
* 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.web.reactive.result.method.annotation;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Optional;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.Errors;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.EnableWebReactive;
import static org.junit.Assert.assertEquals;
/**
* Data binding and type conversion related integration tests for
* {@code @Controller}-annotated classes.
*
* @author Rossen Stoyanchev
*/
public class RequestMappingDataBindingIntegrationTests extends AbstractRequestMappingIntegrationTests {
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
return wac;
}
@Test
public void handleDateParam() throws Exception {
assertEquals("Processed date!",
performPost("/date-param?date=2016-10-31&date-pattern=YYYY-mm-dd",
new HttpHeaders(), null, String.class).getBody());
}
@Test
public void handleForm() throws Exception {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("name", "George");
formData.add("age", "5");
assertEquals("Processed form: Foo[id=1, name='George', age=5]",
performPost("/foos/1", MediaType.APPLICATION_FORM_URLENCODED, formData,
MediaType.TEXT_PLAIN, String.class).getBody());
}
@Configuration
@EnableWebReactive
@ComponentScan(resourcePattern = "**/RequestMappingDataBindingIntegrationTests*.class")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig {
}
@RestController
@SuppressWarnings({"unused", "OptionalUsedAsFieldOrParameterType"})
private static class TestController {
@InitBinder
public void initBinder(WebDataBinder binder,
@RequestParam("date-pattern") Optional<String> optionalPattern) {
optionalPattern.ifPresent(pattern -> {
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(pattern), false);
binder.registerCustomEditor(Date.class, dateEditor);
});
}
@PostMapping("/date-param")
public String handleDateParam(@RequestParam Date date) {
return "Processed date!";
}
@ModelAttribute
public Mono<Foo> addFooAttribute(@PathVariable("id") Optional<Long> optiponalId) {
return optiponalId.map(id -> Mono.just(new Foo(id))).orElse(Mono.empty());
}
@PostMapping("/foos/{id}")
public String handleForm(@ModelAttribute Foo foo, Errors errors) {
return (errors.hasErrors() ?
"Form not processed" : "Processed form: " + foo);
}
}
private static class Foo {
private final Long id;
private String name;
private int age;
public Foo(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Foo[id=" + this.id + ", name='" + this.name + "', age=" + this.age + "]";
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.web.reactive.result.method.annotation;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.EnableWebReactive;
import static org.junit.Assert.assertEquals;
/**
* {@code @RequestMapping} integration tests with exception handling scenarios.
*
* @author Rossen Stoyanchev
*/
public class RequestMappingExceptionHandlingIntegrationTests extends AbstractRequestMappingIntegrationTests {
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
return wac;
}
@Test
public void controllerThrowingException() throws Exception {
String expected = "Recovered from error: State";
assertEquals(expected, performGet("/thrown-exception", new HttpHeaders(), String.class).getBody());
}
@Test
public void controllerReturnsMonoError() throws Exception {
String expected = "Recovered from error: Argument";
assertEquals(expected, performGet("/mono-error", new HttpHeaders(), String.class).getBody());
}
@Configuration
@EnableWebReactive
@ComponentScan(resourcePattern = "**/RequestMappingExceptionHandlingIntegrationTests$*.class")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig {
}
@RestController
@SuppressWarnings("unused")
private static class TestController {
@GetMapping("/thrown-exception")
public Publisher<String> handleAndThrowException() {
throw new IllegalStateException("State");
}
@GetMapping("/mono-error")
public Publisher<String> handleWithError() {
return Mono.error(new IllegalArgumentException("Argument"));
}
@ExceptionHandler
public Publisher<String> handleArgumentException(IllegalArgumentException ex) {
return Mono.just("Recovered from error: " + ex.getMessage());
}
@ExceptionHandler
public ResponseEntity<Publisher<String>> handleStateException(IllegalStateException ex) {
return ResponseEntity.ok(Mono.just("Recovered from error: " + ex.getMessage()));
}
}
}

View File

@@ -0,0 +1,257 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.reactive.accept.MappingContentTypeResolver;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link RequestMappingHandlerMapping}.
*
* @author Rossen Stoyanchev
*/
public class RequestMappingHandlerMappingTests {
private final StaticWebApplicationContext wac = new StaticWebApplicationContext();
private final RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
@Before
public void setUp() throws Exception {
this.handlerMapping.setApplicationContext(wac);
}
@Test
public void useRegisteredSuffixPatternMatch() {
assertTrue(this.handlerMapping.useSuffixPatternMatch());
assertTrue(this.handlerMapping.useRegisteredSuffixPatternMatch());
MappingContentTypeResolver contentTypeResolver = mock(MappingContentTypeResolver.class);
when(contentTypeResolver.getKeys()).thenReturn(Collections.singleton("json"));
this.handlerMapping.setContentTypeResolver(contentTypeResolver);
this.handlerMapping.afterPropertiesSet();
assertTrue(this.handlerMapping.useSuffixPatternMatch());
assertTrue(this.handlerMapping.useRegisteredSuffixPatternMatch());
assertEquals(Collections.singleton("json"), this.handlerMapping.getFileExtensions());
}
@Test
public void useRegisteredSuffixPatternMatchInitialization() {
MappingContentTypeResolver contentTypeResolver = mock(MappingContentTypeResolver.class);
when(contentTypeResolver.getKeys()).thenReturn(Collections.singleton("json"));
final Set<String> actualExtensions = new HashSet<>();
RequestMappingHandlerMapping localHandlerMapping = new RequestMappingHandlerMapping() {
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
actualExtensions.addAll(getFileExtensions());
return super.getMappingForMethod(method, handlerType);
}
};
this.wac.registerSingleton("testController", ComposedAnnotationController.class);
this.wac.refresh();
localHandlerMapping.setContentTypeResolver(contentTypeResolver);
localHandlerMapping.setUseRegisteredSuffixPatternMatch(true);
localHandlerMapping.setApplicationContext(this.wac);
localHandlerMapping.afterPropertiesSet();
assertEquals(Collections.singleton("json"), actualExtensions);
}
@Test
public void useSuffixPatternMatch() {
assertTrue(this.handlerMapping.useSuffixPatternMatch());
assertTrue(this.handlerMapping.useRegisteredSuffixPatternMatch());
this.handlerMapping.setUseSuffixPatternMatch(false);
assertFalse(this.handlerMapping.useSuffixPatternMatch());
this.handlerMapping.setUseRegisteredSuffixPatternMatch(false);
assertFalse("'false' registeredSuffixPatternMatch shouldn't impact suffixPatternMatch",
this.handlerMapping.useSuffixPatternMatch());
this.handlerMapping.setUseRegisteredSuffixPatternMatch(true);
assertTrue("'true' registeredSuffixPatternMatch should enable suffixPatternMatch",
this.handlerMapping.useSuffixPatternMatch());
}
@Test
public void resolveEmbeddedValuesInPatterns() {
this.handlerMapping.setEmbeddedValueResolver(
value -> "/${pattern}/bar".equals(value) ? "/foo/bar" : value
);
String[] patterns = new String[] { "/foo", "/${pattern}/bar" };
String[] result = this.handlerMapping.resolveEmbeddedValuesInPatterns(patterns);
assertArrayEquals(new String[] { "/foo", "/foo/bar" }, result);
}
@Test
public void resolveRequestMappingViaComposedAnnotation() throws Exception {
RequestMappingInfo info = assertComposedAnnotationMapping("postJson", "/postJson", RequestMethod.POST);
assertEquals(MediaType.APPLICATION_JSON_VALUE,
info.getConsumesCondition().getConsumableMediaTypes().iterator().next().toString());
assertEquals(MediaType.APPLICATION_JSON_VALUE,
info.getProducesCondition().getProducibleMediaTypes().iterator().next().toString());
}
@Test // SPR-14988
public void getMappingOverridesConsumesFromTypeLevelAnnotation() throws Exception {
RequestMappingInfo requestMappingInfo = assertComposedAnnotationMapping(RequestMethod.GET);
assertArrayEquals(new MediaType[]{MediaType.ALL}, new ArrayList<>(
requestMappingInfo.getConsumesCondition().getConsumableMediaTypes()).toArray());
}
@Test
public void getMapping() throws Exception {
assertComposedAnnotationMapping(RequestMethod.GET);
}
@Test
public void postMapping() throws Exception {
assertComposedAnnotationMapping(RequestMethod.POST);
}
@Test
public void putMapping() throws Exception {
assertComposedAnnotationMapping(RequestMethod.PUT);
}
@Test
public void deleteMapping() throws Exception {
assertComposedAnnotationMapping(RequestMethod.DELETE);
}
@Test
public void patchMapping() throws Exception {
assertComposedAnnotationMapping(RequestMethod.PATCH);
}
private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) throws Exception {
String methodName = requestMethod.name().toLowerCase();
String path = "/" + methodName;
return assertComposedAnnotationMapping(methodName, path, requestMethod);
}
private RequestMappingInfo assertComposedAnnotationMapping(String methodName, String path,
RequestMethod requestMethod) throws Exception {
Class<?> clazz = ComposedAnnotationController.class;
Method method = clazz.getMethod(methodName);
RequestMappingInfo info = this.handlerMapping.getMappingForMethod(method, clazz);
assertNotNull(info);
Set<String> paths = info.getPatternsCondition().getPatterns();
assertEquals(1, paths.size());
assertEquals(path, paths.iterator().next());
Set<RequestMethod> methods = info.getMethodsCondition().getMethods();
assertEquals(1, methods.size());
assertEquals(requestMethod, methods.iterator().next());
return info;
}
@Controller @SuppressWarnings("unused")
@RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
static class ComposedAnnotationController {
@RequestMapping
public void handle() {
}
@PostJson("/postJson")
public void postJson() {
}
@GetMapping(value = "/get", consumes = MediaType.ALL_VALUE)
public void get() {
}
@PostMapping("/post")
public void post() {
}
@PutMapping("/put")
public void put() {
}
@DeleteMapping("/delete")
public void delete() {
}
@PatchMapping("/patch")
public void patch() {
}
}
@RequestMapping(method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface PostJson {
@AliasFor(annotation = RequestMapping.class, attribute = "path") @SuppressWarnings("unused")
String[] value() default {};
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.web.reactive.result.method.annotation;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.EnableWebReactive;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* Integration tests with {@code @RequestMapping} handler methods.
*
* <p>Before adding tests here consider if they are a better fit for any of the
* other {@code RequestMapping*IntegrationTests}.
*
* @author Rossen Stoyanchev
* @author Stephane Maldini
*/
public class RequestMappingIntegrationTests extends AbstractRequestMappingIntegrationTests {
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
return wac;
}
@Test
public void handleWithParam() throws Exception {
String expected = "Hello George!";
assertEquals(expected, performGet("/param?name=George", new HttpHeaders(), String.class).getBody());
}
@Test // SPR-15140
public void handleWithEncodedParam() throws Exception {
String expected = "Hello ++\u00e0!";
assertEquals(expected, performGet("/param?name=%20%2B+%C3%A0", new HttpHeaders(), String.class).getBody());
}
@Test
public void longStreamResult() throws Exception {
String[] expected = {"0", "1", "2", "3", "4"};
assertArrayEquals(expected, performGet("/long-stream-result", new HttpHeaders(), String[].class).getBody());
}
@Test
public void objectStreamResultWithAllMediaType() throws Exception {
String expected = "[{\"name\":\"bar\"}]";
assertEquals(expected, performGet("/object-stream-result", MediaType.ALL, String.class).getBody());
}
@Configuration
@EnableWebReactive
@ComponentScan(resourcePattern = "**/RequestMappingIntegrationTests$*.class")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig {
}
@RestController
@SuppressWarnings("unused")
private static class TestRestController {
@GetMapping("/param")
public Publisher<String> handleWithParam(@RequestParam String name) {
return Flux.just("Hello ", name, "!");
}
@GetMapping("/long-stream-result")
public Publisher<Long> longStreamResponseBody() {
return Flux.intervalMillis(100).take(5);
}
@GetMapping("/object-stream-result")
public Publisher<Foo> objectStreamResponseBody() {
return Flux.just(new Foo("bar"));
}
}
private static class Foo {
private String name;
public Foo() {
}
public Foo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -0,0 +1,671 @@
/*
* 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.web.reactive.result.method.annotation;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.Completable;
import rx.Observable;
import rx.Single;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.ClassPathResource;
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.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.server.reactive.ZeroCopyIntegrationTests;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.EnableWebReactive;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.MediaType.APPLICATION_XML;
/**
* {@code @RequestMapping} integration tests focusing on serialization and
* deserialization of the request and response body.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
public class RequestMappingMessageConversionIntegrationTests extends AbstractRequestMappingIntegrationTests {
private static final ParameterizedTypeReference<List<Person>> PERSON_LIST =
new ParameterizedTypeReference<List<Person>>() {};
private static final MediaType JSON = MediaType.APPLICATION_JSON;
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
return wac;
}
@Test
public void byteBufferResponseBodyWithPublisher() throws Exception {
Person expected = new Person("Robert");
assertEquals(expected, performGet("/raw-response/publisher", JSON, Person.class).getBody());
}
@Test
public void byteBufferResponseBodyWithFlux() throws Exception {
String expected = "Hello!";
assertEquals(expected, performGet("/raw-response/flux", new HttpHeaders(), String.class).getBody());
}
@Test
public void byteBufferResponseBodyWithObservable() throws Exception {
String expected = "Hello!";
assertEquals(expected, performGet("/raw-response/observable", new HttpHeaders(), String.class).getBody());
}
@Test
public void byteBufferResponseBodyWithRxJava2Observable() throws Exception {
String expected = "Hello!";
assertEquals(expected, performGet("/raw-response/rxjava2-observable",
new HttpHeaders(), String.class).getBody());
}
@Test
public void byteBufferResponseBodyWithFlowable() throws Exception {
String expected = "Hello!";
assertEquals(expected, performGet("/raw-response/flowable", new HttpHeaders(), String.class).getBody());
}
@Test
public void personResponseBody() throws Exception {
Person expected = new Person("Robert");
assertEquals(expected, performGet("/person-response/person", JSON, Person.class).getBody());
}
@Test
public void personResponseBodyWithCompletableFuture() throws Exception {
Person expected = new Person("Robert");
assertEquals(expected, performGet("/person-response/completable-future", JSON, Person.class).getBody());
}
@Test
public void personResponseBodyWithMono() throws Exception {
Person expected = new Person("Robert");
assertEquals(expected, performGet("/person-response/mono", JSON, Person.class).getBody());
}
@Test
public void personResponseBodyWithSingle() throws Exception {
Person expected = new Person("Robert");
assertEquals(expected, performGet("/person-response/single", JSON, Person.class).getBody());
}
@Test
public void personResponseBodyWithMonoResponseEntity() throws Exception {
Person expected = new Person("Robert");
assertEquals(expected, performGet("/person-response/mono-response-entity", JSON, Person.class).getBody());
}
@Test
public void personResponseBodyWithList() throws Exception {
List<?> expected = asList(new Person("Robert"), new Person("Marie"));
assertEquals(expected, performGet("/person-response/list", JSON, PERSON_LIST).getBody());
}
@Test
public void personResponseBodyWithPublisher() throws Exception {
List<?> expected = asList(new Person("Robert"), new Person("Marie"));
assertEquals(expected, performGet("/person-response/publisher", JSON, PERSON_LIST).getBody());
}
@Test
public void personResponseBodyWithFlux() throws Exception {
List<?> expected = asList(new Person("Robert"), new Person("Marie"));
assertEquals(expected, performGet("/person-response/flux", JSON, PERSON_LIST).getBody());
}
@Test
public void personResponseBodyWithObservable() throws Exception {
List<?> expected = asList(new Person("Robert"), new Person("Marie"));
assertEquals(expected, performGet("/person-response/observable", JSON, PERSON_LIST).getBody());
}
@Test
public void resource() throws Exception {
ResponseEntity<byte[]> response = performGet("/resource", new HttpHeaders(), byte[].class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.hasBody());
assertEquals(951, response.getHeaders().getContentLength());
assertEquals(951, response.getBody().length);
assertEquals(new MediaType("image", "x-png"), response.getHeaders().getContentType());
}
@Test
public void personTransform() throws Exception {
assertEquals(new Person("ROBERT"),
performPost("/person-transform/person", JSON, new Person("Robert"),
JSON, Person.class).getBody());
}
@Test
public void personTransformWithCompletableFuture() throws Exception {
assertEquals(new Person("ROBERT"),
performPost("/person-transform/completable-future", JSON, new Person("Robert"),
JSON, Person.class).getBody());
}
@Test
public void personTransformWithMono() throws Exception {
assertEquals(new Person("ROBERT"),
performPost("/person-transform/mono", JSON, new Person("Robert"),
JSON, Person.class).getBody());
}
@Test
public void personTransformWithSingle() throws Exception {
assertEquals(new Person("ROBERT"),
performPost("/person-transform/single", JSON, new Person("Robert"),
JSON, Person.class).getBody());
}
@Test
public void personTransformWithRxJava2Single() throws Exception {
assertEquals(new Person("ROBERT"),
performPost("/person-transform/rxjava2-single", JSON, new Person("Robert"),
JSON, Person.class).getBody());
}
@Test
public void personTransformWithRxJava2Maybe() throws Exception {
assertEquals(new Person("ROBERT"),
performPost("/person-transform/rxjava2-maybe", JSON, new Person("Robert"),
JSON, Person.class).getBody());
}
@Test
public void personTransformWithPublisher() throws Exception {
List<?> req = asList(new Person("Robert"), new Person("Marie"));
List<?> res = asList(new Person("ROBERT"), new Person("MARIE"));
assertEquals(res, performPost("/person-transform/publisher", JSON, req, JSON, PERSON_LIST).getBody());
}
@Test
public void personTransformWithFlux() throws Exception {
List<?> req = asList(new Person("Robert"), new Person("Marie"));
List<?> res = asList(new Person("ROBERT"), new Person("MARIE"));
assertEquals(res, performPost("/person-transform/flux", JSON, req, JSON, PERSON_LIST).getBody());
}
@Test
public void personTransformWithObservable() throws Exception {
List<?> req = asList(new Person("Robert"), new Person("Marie"));
List<?> res = asList(new Person("ROBERT"), new Person("MARIE"));
assertEquals(res, performPost("/person-transform/observable", JSON, req, JSON, PERSON_LIST).getBody());
}
@Test
public void personTransformWithRxJava2Observable() throws Exception {
List<?> req = asList(new Person("Robert"), new Person("Marie"));
List<?> res = asList(new Person("ROBERT"), new Person("MARIE"));
assertEquals(res, performPost("/person-transform/rxjava2-observable", JSON, req, JSON, PERSON_LIST).getBody());
}
@Test
public void personTransformWithFlowable() throws Exception {
List<?> req = asList(new Person("Robert"), new Person("Marie"));
List<?> res = asList(new Person("ROBERT"), new Person("MARIE"));
assertEquals(res, performPost("/person-transform/flowable", JSON, req, JSON, PERSON_LIST).getBody());
}
@Test
public void personCreateWithPublisherJson() throws Exception {
ResponseEntity<Void> entity = performPost("/person-create/publisher", JSON,
asList(new Person("Robert"), new Person("Marie")), null, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithPublisherXml() throws Exception {
People people = new People(new Person("Robert"), new Person("Marie"));
ResponseEntity<Void> response = performPost("/person-create/publisher", APPLICATION_XML, people, null, Void.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithMono() throws Exception {
ResponseEntity<Void> entity = performPost(
"/person-create/mono", JSON, new Person("Robert"), null, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(1, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithSingle() throws Exception {
ResponseEntity<Void> entity = performPost(
"/person-create/single", JSON, new Person("Robert"), null, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(1, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithRxJava2Single() throws Exception {
ResponseEntity<Void> entity = performPost(
"/person-create/rxjava2-single", JSON, new Person("Robert"), null, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(1, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithFluxJson() throws Exception {
ResponseEntity<Void> entity = performPost("/person-create/flux", JSON,
asList(new Person("Robert"), new Person("Marie")), null, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithFluxXml() throws Exception {
People people = new People(new Person("Robert"), new Person("Marie"));
ResponseEntity<Void> response = performPost("/person-create/flux", APPLICATION_XML, people, null, Void.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithObservableJson() throws Exception {
ResponseEntity<Void> entity = performPost("/person-create/observable", JSON,
asList(new Person("Robert"), new Person("Marie")), null, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithRxJava2ObservableJson() throws Exception {
ResponseEntity<Void> entity = performPost("/person-create/rxjava2-observable", JSON,
asList(new Person("Robert"), new Person("Marie")), null, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithObservableXml() throws Exception {
People people = new People(new Person("Robert"), new Person("Marie"));
ResponseEntity<Void> response = performPost("/person-create/observable", APPLICATION_XML, people, null, Void.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithRxJava2ObservableXml() throws Exception {
People people = new People(new Person("Robert"), new Person("Marie"));
ResponseEntity<Void> response = performPost("/person-create/rxjava2-observable", APPLICATION_XML, people, null, Void.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithFlowableJson() throws Exception {
ResponseEntity<Void> entity = performPost("/person-create/flowable", JSON,
asList(new Person("Robert"), new Person("Marie")), null, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Test
public void personCreateWithFlowableXml() throws Exception {
People people = new People(new Person("Robert"), new Person("Marie"));
ResponseEntity<Void> response = performPost("/person-create/flowable", APPLICATION_XML, people, null, Void.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(2, getApplicationContext().getBean(PersonCreateController.class).persons.size());
}
@Configuration
@EnableWebReactive
@ComponentScan(resourcePattern = "**/RequestMappingMessageConversionIntegrationTests$*.class")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig {
}
@RestController
@RequestMapping("/raw-response")
@SuppressWarnings("unused")
private static class RawResponseBodyController {
@GetMapping("/publisher")
public Publisher<ByteBuffer> getPublisher() {
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
return encoder.encode(Mono.just(new Person("Robert")), dataBufferFactory,
ResolvableType.forClass(Person.class), JSON, Collections.emptyMap()).map(DataBuffer::asByteBuffer);
}
@GetMapping("/flux")
public Flux<ByteBuffer> getFlux() {
return Flux.just(ByteBuffer.wrap("Hello!".getBytes()));
}
@GetMapping("/observable")
public Observable<ByteBuffer> getObservable() {
return Observable.just(ByteBuffer.wrap("Hello!".getBytes()));
}
@GetMapping("/rxjava2-observable")
public io.reactivex.Observable<ByteBuffer> getRxJava2Observable() {
return io.reactivex.Observable.just(ByteBuffer.wrap("Hello!".getBytes()));
}
@GetMapping("/flowable")
public Flowable<ByteBuffer> getFlowable() {
return Flowable.just(ByteBuffer.wrap("Hello!".getBytes()));
}
}
@RestController
@RequestMapping("/person-response")
@SuppressWarnings("unused")
private static class PersonResponseBodyController {
@GetMapping("/person")
public Person getPerson() {
return new Person("Robert");
}
@GetMapping("/completable-future")
public CompletableFuture<Person> getCompletableFuture() {
return CompletableFuture.completedFuture(new Person("Robert"));
}
@GetMapping("/mono")
public Mono<Person> getMono() {
return Mono.just(new Person("Robert"));
}
@GetMapping("/single")
public Single<Person> getSingle() {
return Single.just(new Person("Robert"));
}
@GetMapping("/mono-response-entity")
public ResponseEntity<Mono<Person>> getMonoResponseEntity() {
Mono<Person> body = Mono.just(new Person("Robert"));
return ResponseEntity.ok(body);
}
@GetMapping("/list")
public List<Person> getList() {
return asList(new Person("Robert"), new Person("Marie"));
}
@GetMapping("/publisher")
public Publisher<Person> getPublisher() {
return Flux.just(new Person("Robert"), new Person("Marie"));
}
@GetMapping("/flux")
public Flux<Person> getFlux() {
return Flux.just(new Person("Robert"), new Person("Marie"));
}
@GetMapping("/observable")
public Observable<Person> getObservable() {
return Observable.just(new Person("Robert"), new Person("Marie"));
}
}
@RestController
@SuppressWarnings("unused")
private static class ResourceController {
@GetMapping("/resource")
public Resource resource() {
return new ClassPathResource("spring.png", ZeroCopyIntegrationTests.class);
}
}
@RestController
@RequestMapping("/person-transform")
@SuppressWarnings("unused")
private static class PersonTransformationController {
@PostMapping("/person")
public Person transformPerson(@RequestBody Person person) {
return new Person(person.getName().toUpperCase());
}
@PostMapping("/completable-future")
public CompletableFuture<Person> transformCompletableFuture(
@RequestBody CompletableFuture<Person> personFuture) {
return personFuture.thenApply(person -> new Person(person.getName().toUpperCase()));
}
@PostMapping("/mono")
public Mono<Person> transformMono(@RequestBody Mono<Person> personFuture) {
return personFuture.map(person -> new Person(person.getName().toUpperCase()));
}
@PostMapping("/single")
public Single<Person> transformSingle(@RequestBody Single<Person> personFuture) {
return personFuture.map(person -> new Person(person.getName().toUpperCase()));
}
@PostMapping("/rxjava2-single")
public io.reactivex.Single<Person> transformRxJava2Single(@RequestBody io.reactivex.Single<Person> personFuture) {
return personFuture.map(person -> new Person(person.getName().toUpperCase()));
}
@PostMapping("/rxjava2-maybe")
public Maybe<Person> transformRxJava2Maybe(@RequestBody Maybe<Person> personFuture) {
return personFuture.map(person -> new Person(person.getName().toUpperCase()));
}
@PostMapping("/publisher")
public Publisher<Person> transformPublisher(@RequestBody Publisher<Person> persons) {
return Flux
.from(persons)
.map(person -> new Person(person.getName().toUpperCase()));
}
@PostMapping("/flux")
public Flux<Person> transformFlux(@RequestBody Flux<Person> persons) {
return persons.map(person -> new Person(person.getName().toUpperCase()));
}
@PostMapping("/observable")
public Observable<Person> transformObservable(@RequestBody Observable<Person> persons) {
return persons.map(person -> new Person(person.getName().toUpperCase()));
}
@PostMapping("/rxjava2-observable")
public io.reactivex.Observable<Person> transformObservable(@RequestBody io.reactivex.Observable<Person> persons) {
return persons.map(person -> new Person(person.getName().toUpperCase()));
}
@PostMapping("/flowable")
public Flowable<Person> transformFlowable(@RequestBody Flowable<Person> persons) {
return persons.map(person -> new Person(person.getName().toUpperCase()));
}
}
@RestController
@RequestMapping("/person-create")
@SuppressWarnings("unused")
private static class PersonCreateController {
final List<Person> persons = new ArrayList<>();
@PostMapping("/publisher")
public Publisher<Void> createWithPublisher(@RequestBody Publisher<Person> publisher) {
return Flux.from(publisher).doOnNext(persons::add).then();
}
@PostMapping("/mono")
public Mono<Void> createWithMono(@RequestBody Mono<Person> mono) {
return mono.doOnNext(persons::add).then();
}
@PostMapping("/single")
public Completable createWithSingle(@RequestBody Single<Person> single) {
return single.map(persons::add).toCompletable();
}
@PostMapping("/rxjava2-single")
public io.reactivex.Completable createWithRxJava2Single(@RequestBody io.reactivex.Single<Person> single) {
return single.map(persons::add).toCompletable();
}
@PostMapping("/flux")
public Mono<Void> createWithFlux(@RequestBody Flux<Person> flux) {
return flux.doOnNext(persons::add).then();
}
@PostMapping("/observable")
public Observable<Void> createWithObservable(@RequestBody Observable<Person> observable) {
return observable.toList().doOnNext(persons::addAll).flatMap(document -> Observable.empty());
}
@PostMapping("/rxjava2-observable")
public io.reactivex.Completable createWithRxJava2Observable(@RequestBody io.reactivex.Observable<Person> observable) {
return observable.toList().doOnSuccess(persons::addAll).toCompletable();
}
@PostMapping("/flowable")
public io.reactivex.Completable createWithFlowable(@RequestBody Flowable<Person> flowable) {
return flowable.toList().doOnSuccess(persons::addAll).toCompletable();
}
}
@XmlRootElement
@SuppressWarnings("WeakerAccess")
private static class Person {
private String name;
@SuppressWarnings("unused")
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Person person = (Person) o;
return !(this.name != null ? !this.name.equals(person.name) : person.name != null);
}
@Override
public int hashCode() {
return this.name != null ? this.name.hashCode() : 0;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
@XmlRootElement
@SuppressWarnings({"WeakerAccess", "unused"})
private static class People {
private List<Person> persons = new ArrayList<>();
public People() {
}
public People(Person... persons) {
this.persons.addAll(Arrays.asList(persons));
}
@XmlElement
public List<Person> getPerson() {
return this.persons;
}
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.web.reactive.result.method.annotation;
import java.net.URI;
import java.util.Optional;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.reactive.config.ViewResolverRegistry;
import org.springframework.web.reactive.config.WebReactiveConfigurationSupport;
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.springframework.http.RequestEntity.get;
/**
* {@code @RequestMapping} integration tests with view resolution scenarios.
*
* @author Rossen Stoyanchev
*/
public class RequestMappingViewResolutionIntegrationTests extends AbstractRequestMappingIntegrationTests {
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
return wac;
}
@Test
public void html() throws Exception {
String expected = "<html><body>Hello: Jason!</body></html>";
assertEquals(expected, performGet("/html?name=Jason", MediaType.TEXT_HTML, String.class).getBody());
}
@Test
public void etagCheckWithNotModifiedResponse() throws Exception {
URI uri = new URI("http://localhost:" + this.port + "/html");
RequestEntity<Void> request = get(uri).ifNoneMatch("\"deadb33f8badf00d\"").build();
ResponseEntity<String> response = getRestTemplate().exchange(request, String.class);
assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode());
assertNull(response.getBody());
}
@Configuration
@ComponentScan(resourcePattern = "**/RequestMappingViewResolutionIntegrationTests$*.class")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig extends WebReactiveConfigurationSupport {
@Override
protected void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
}
@Bean
public FreeMarkerConfigurer freeMarkerConfig() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setPreferFileSystemAccess(false);
configurer.setTemplateLoaderPath("classpath*:org/springframework/web/reactive/view/freemarker/");
return configurer;
}
}
@Controller
@SuppressWarnings("unused")
private static class TestController {
@GetMapping("/html")
public String getHtmlPage(@RequestParam Optional<String> name, Model model,
ServerWebExchange exchange) {
if (exchange.checkNotModified("deadb33f8badf00d")) {
return null;
}
model.addAttribute("hello", "Hello: " + name.orElse("<no name>") + "!");
return "test";
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link RequestParamMapMethodArgumentResolver}.
* @author Rossen Stoyanchev
*/
public class RequestParamMapMethodArgumentResolverTests {
private RequestParamMapMethodArgumentResolver resolver;
private MethodParameter paramMap;
private MethodParameter paramMultiValueMap;
private MethodParameter paramNamedMap;
private MethodParameter paramMapWithoutAnnot;
@Before
public void setUp() throws Exception {
this.resolver = new RequestParamMapMethodArgumentResolver();
Method method = getClass().getMethod("params", Map.class, MultiValueMap.class, Map.class, Map.class);
this.paramMap = new SynthesizingMethodParameter(method, 0);
this.paramMultiValueMap = new SynthesizingMethodParameter(method, 1);
this.paramNamedMap = new SynthesizingMethodParameter(method, 2);
this.paramMapWithoutAnnot = new SynthesizingMethodParameter(method, 3);
}
@Test
public void supportsParameter() {
assertTrue(this.resolver.supportsParameter(this.paramMap));
assertTrue(this.resolver.supportsParameter(this.paramMultiValueMap));
assertFalse(this.resolver.supportsParameter(this.paramNamedMap));
assertFalse(this.resolver.supportsParameter(this.paramMapWithoutAnnot));
}
@Test
public void resolveMapArgumentWithQueryString() throws Exception {
Object result= resolve(this.paramMap, exchangeWithQuery("foo=bar"));
assertTrue(result instanceof Map);
assertEquals(Collections.singletonMap("foo", "bar"), result);
}
@Test
public void resolveMapArgumentWithFormData() throws Exception {
Object result= resolve(this.paramMap, exchangeWithFormData("foo=bar"));
assertTrue(result instanceof Map);
assertEquals(Collections.singletonMap("foo", "bar"), result);
}
@Test
public void resolveMultiValueMapArgument() throws Exception {
ServerWebExchange exchange = exchangeWithQuery("foo=bar&foo=baz");
Object result= resolve(this.paramMultiValueMap, exchange);
assertTrue(result instanceof MultiValueMap);
assertEquals(Collections.singletonMap("foo", Arrays.asList("bar", "baz")), result);
}
private ServerWebExchange exchangeWithQuery(String query) throws URISyntaxException {
MockServerHttpRequest request = MockServerHttpRequest.get("/path?" + query).build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
private ServerWebExchange exchangeWithFormData(String formData) throws URISyntaxException {
MockServerHttpRequest request = MockServerHttpRequest.post("/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(formData);
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
private Object resolve(MethodParameter parameter, ServerWebExchange exchange) {
return this.resolver.resolveArgument(parameter, null, exchange).blockMillis(0);
}
@SuppressWarnings("unused")
public void params(@RequestParam Map<?, ?> param1,
@RequestParam MultiValueMap<?, ?> param2,
@RequestParam("name") Map<?, ?> param3,
Map<?, ?> param4) {
}
}

View File

@@ -0,0 +1,226 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link RequestParamMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class RequestParamMethodArgumentResolverTests {
private RequestParamMethodArgumentResolver resolver;
private MethodParameter paramNamedDefaultValueString;
private MethodParameter paramNamedStringArray;
private MethodParameter paramNamedMap;
private MethodParameter paramMap;
private MethodParameter paramStringNotAnnot;
private MethodParameter paramRequired;
private MethodParameter paramNotRequired;
private MethodParameter paramOptional;
private BindingContext bindContext;
@Before @SuppressWarnings("ConfusingArgumentToVarargsMethod")
public void setUp() throws Exception {
this.resolver = new RequestParamMethodArgumentResolver(null, true);
ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
Method method = ReflectionUtils.findMethod(getClass(), "handle", (Class<?>[]) null);
this.paramNamedDefaultValueString = new SynthesizingMethodParameter(method, 0);
this.paramNamedStringArray = new SynthesizingMethodParameter(method, 1);
this.paramNamedMap = new SynthesizingMethodParameter(method, 2);
this.paramMap = new SynthesizingMethodParameter(method, 3);
this.paramStringNotAnnot = new SynthesizingMethodParameter(method, 4);
this.paramStringNotAnnot.initParameterNameDiscovery(paramNameDiscoverer);
this.paramRequired = new SynthesizingMethodParameter(method, 5);
this.paramNotRequired = new SynthesizingMethodParameter(method, 6);
this.paramOptional = new SynthesizingMethodParameter(method, 7);
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(new DefaultFormattingConversionService());
this.bindContext = new BindingContext(initializer);
}
@Test
public void supportsParameter() {
this.resolver = new RequestParamMethodArgumentResolver(null, true);
assertTrue(this.resolver.supportsParameter(this.paramNamedDefaultValueString));
assertTrue(this.resolver.supportsParameter(this.paramNamedStringArray));
assertTrue(this.resolver.supportsParameter(this.paramNamedMap));
assertFalse(this.resolver.supportsParameter(this.paramMap));
assertTrue(this.resolver.supportsParameter(this.paramStringNotAnnot));
assertTrue(this.resolver.supportsParameter(this.paramRequired));
assertTrue(this.resolver.supportsParameter(this.paramNotRequired));
assertTrue(this.resolver.supportsParameter(this.paramOptional));
this.resolver = new RequestParamMethodArgumentResolver(null, false);
assertFalse(this.resolver.supportsParameter(this.paramStringNotAnnot));
}
@Test
public void resolveWithQueryString() throws Exception {
assertEquals("foo", resolve(this.paramNamedDefaultValueString, exchangeWithQuery("name=foo")));
}
@Test
public void resolveWithFormData() throws Exception {
assertEquals("foo", resolve(this.paramNamedDefaultValueString, exchangeWithFormData("name=foo")));
}
@Test
public void resolveStringArray() throws Exception {
Object result = resolve(this.paramNamedStringArray, exchangeWithQuery("name=foo&name=bar"));
assertTrue(result instanceof String[]);
assertArrayEquals(new String[] {"foo", "bar"}, (String[]) result);
}
@Test
public void resolveDefaultValue() throws Exception {
Object result = resolve(this.paramNamedDefaultValueString, exchange());
assertEquals("bar", result);
}
@Test
public void missingRequestParam() throws Exception {
Mono<Object> mono = this.resolver.resolveArgument(
this.paramNamedStringArray, this.bindContext, exchange());
StepVerifier.create(mono)
.expectNextCount(0)
.expectError(ServerWebInputException.class)
.verify();
}
@Test
public void resolveSimpleTypeParam() throws Exception {
ServerWebExchange exchange = exchangeWithQuery("stringNotAnnot=plainValue");
Object result = resolve(this.paramStringNotAnnot, exchange);
assertEquals("plainValue", result);
}
@Test // SPR-8561
public void resolveSimpleTypeParamToNull() throws Exception {
assertNull(resolve(this.paramStringNotAnnot, exchange()));
}
@Test // SPR-10180
public void resolveEmptyValueToDefault() throws Exception {
ServerWebExchange exchange = exchangeWithQuery("name=");
Object result = resolve(this.paramNamedDefaultValueString, exchange);
assertEquals("bar", result);
}
@Test
public void resolveEmptyValueWithoutDefault() throws Exception {
assertEquals("", resolve(this.paramStringNotAnnot, exchangeWithQuery("stringNotAnnot=")));
}
@Test
public void resolveEmptyValueRequiredWithoutDefault() throws Exception {
assertEquals("", resolve(this.paramRequired, exchangeWithQuery("name=")));
}
@Test
public void resolveOptionalParamValue() throws Exception {
ServerWebExchange exchange = exchange();
Object result = resolve(this.paramOptional, exchange);
assertEquals(Optional.empty(), result);
exchange = exchangeWithQuery("name=123");
result = resolve(this.paramOptional, exchange);
assertEquals(Optional.class, result.getClass());
Optional<?> value = (Optional<?>) result;
assertTrue(value.isPresent());
assertEquals(123, value.get());
}
private ServerWebExchange exchangeWithQuery(String query) throws URISyntaxException {
MockServerHttpRequest request = MockServerHttpRequest.get("/path?" + query).build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
private ServerWebExchange exchangeWithFormData(String formData) throws URISyntaxException {
MockServerHttpRequest request = MockServerHttpRequest.post("/path")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(formData);
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
private ServerWebExchange exchange() {
MockServerHttpRequest request = MockServerHttpRequest.get("/").build();
return new DefaultServerWebExchange(request, new MockServerHttpResponse());
}
private Object resolve(MethodParameter parameter, ServerWebExchange exchange) {
return this.resolver.resolveArgument(parameter, this.bindContext, exchange).blockMillis(0);
}
@SuppressWarnings({"unused", "OptionalUsedAsFieldOrParameterType"})
public void handle(
@RequestParam(name = "name", defaultValue = "bar") String param1,
@RequestParam("name") String[] param2,
@RequestParam("name") Map<?, ?> param3,
@RequestParam Map<?, ?> param4,
String stringNotAnnot,
@RequestParam("name") String paramRequired,
@RequestParam(name = "name", required = false) String paramNotRequired,
@RequestParam("name") Optional<Integer> paramOptional) {
}
}

View File

@@ -0,0 +1,203 @@
/*
* 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.web.reactive.result.method.annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import rx.Completable;
import rx.Single;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.EncoderHttpMessageWriter;
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.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link ResponseBodyResultHandler}.When adding a test also
* consider whether the logic under test is in a parent class, then see:
* <ul>
* <li>{@code MessageWriterResultHandlerTests},
* <li>{@code ContentNegotiatingResultHandlerSupportTests}
* </ul>
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
*/
public class ResponseBodyResultHandlerTests {
private ResponseBodyResultHandler resultHandler;
private MockServerHttpResponse response;
private ServerWebExchange exchange;
@Before
public void setUp() throws Exception {
this.resultHandler = createHandler();
initExchange();
}
private void initExchange() {
ServerHttpRequest request = MockServerHttpRequest.get("/").build();
this.response = new MockServerHttpResponse();
this.exchange = new DefaultServerWebExchange(request, this.response);
}
private ResponseBodyResultHandler createHandler(HttpMessageWriter<?>... writers) {
List<HttpMessageWriter<?>> writerList;
if (ObjectUtils.isEmpty(writers)) {
writerList = new ArrayList<>();
writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
writerList.add(new ResourceHttpMessageWriter());
writerList.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));
}
else {
writerList = Arrays.asList(writers);
}
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
return new ResponseBodyResultHandler(writerList, resolver);
}
@Test
public void supports() throws NoSuchMethodException {
Object controller = new TestController();
testSupports(controller, "handleToString", true);
testSupports(controller, "doWork", false);
controller = new TestRestController();
testSupports(controller, "handleToString", true);
testSupports(controller, "handleToMonoString", true);
testSupports(controller, "handleToSingleString", true);
testSupports(controller, "handleToCompletable", true);
testSupports(controller, "handleToResponseEntity", false);
testSupports(controller, "handleToMonoResponseEntity", false);
}
@Test
public void writeResponseStatus() throws NoSuchMethodException {
Object controller = new TestRestController();
HandlerMethod hm = handlerMethod(controller, "handleToString");
HandlerResult handlerResult = new HandlerResult(hm, null, hm.getReturnType());
initExchange();
StepVerifier.create(this.resultHandler.handleResult(this.exchange, handlerResult)).expectComplete().verify();
assertEquals(HttpStatus.NO_CONTENT, this.response.getStatusCode());
hm = handlerMethod(controller, "handleToMonoVoid");
handlerResult = new HandlerResult(hm, null, hm.getReturnType());
initExchange();
StepVerifier.create(this.resultHandler.handleResult(this.exchange, handlerResult)).expectComplete().verify();
assertEquals(HttpStatus.CREATED, this.response.getStatusCode());
}
private void testSupports(Object controller, String method, boolean result) throws NoSuchMethodException {
HandlerMethod hm = handlerMethod(controller, method);
HandlerResult handlerResult = new HandlerResult(hm, null, hm.getReturnType());
assertEquals(result, this.resultHandler.supports(handlerResult));
}
@Test
public void defaultOrder() throws Exception {
assertEquals(100, this.resultHandler.getOrder());
}
private HandlerMethod handlerMethod(Object controller, String method) throws NoSuchMethodException {
return new HandlerMethod(controller, controller.getClass().getMethod(method));
}
@RestController @SuppressWarnings("unused")
private static class TestRestController {
@ResponseStatus(code = HttpStatus.CREATED)
public Mono<Void> handleToMonoVoid() { return null;}
@ResponseStatus(code = HttpStatus.NO_CONTENT)
public String handleToString() {
return null;
}
public Mono<String> handleToMonoString() {
return null;
}
public Single<String> handleToSingleString() {
return null;
}
public Completable handleToCompletable() {
return null;
}
public ResponseEntity<String> handleToResponseEntity() {
return null;
}
public Mono<ResponseEntity<String>> handleToMonoResponseEntity() {
return null;
}
}
@Controller @SuppressWarnings("unused")
private static class TestController {
@ResponseBody
public String handleToString() {
return null;
}
public String doWork() {
return null;
}
}
}

View File

@@ -0,0 +1,391 @@
/*
* 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.web.reactive.result.method.annotation;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import rx.Completable;
import rx.Single;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
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.ResponseEntity;
import org.springframework.http.codec.EncoderHttpMessageWriter;
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.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.http.ResponseEntity.notFound;
import static org.springframework.http.ResponseEntity.ok;
/**
* Unit tests for {@link ResponseEntityResultHandler}. When adding a test also
* consider whether the logic under test is in a parent class, then see:
* <ul>
* <li>{@code MessageWriterResultHandlerTests},
* <li>{@code ContentNegotiatingResultHandlerSupportTests}
* </ul>
* @author Rossen Stoyanchev
*/
public class ResponseEntityResultHandlerTests {
private ResponseEntityResultHandler resultHandler;
private MockServerHttpRequest request;
private MockServerHttpResponse response;
@Before
public void setUp() throws Exception {
this.resultHandler = createHandler();
initExchange();
}
private void initExchange() {
this.request = MockServerHttpRequest.get("/path").build();
this.response = new MockServerHttpResponse();
}
private ResponseEntityResultHandler createHandler(HttpMessageWriter<?>... writers) {
List<HttpMessageWriter<?>> writerList;
if (ObjectUtils.isEmpty(writers)) {
writerList = new ArrayList<>();
writerList.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
writerList.add(new ResourceHttpMessageWriter());
writerList.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
writerList.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));
}
else {
writerList = Arrays.asList(writers);
}
RequestedContentTypeResolver resolver = new RequestedContentTypeResolverBuilder().build();
return new ResponseEntityResultHandler(writerList, resolver);
}
@Test
@SuppressWarnings("ConstantConditions")
public void supports() throws NoSuchMethodException {
Object value = null;
ResolvableType type = responseEntity(String.class);
assertTrue(this.resultHandler.supports(handlerResult(value, type)));
type = forClassWithGenerics(Mono.class, responseEntity(String.class));
assertTrue(this.resultHandler.supports(handlerResult(value, type)));
type = forClassWithGenerics(Single.class, responseEntity(String.class));
assertTrue(this.resultHandler.supports(handlerResult(value, type)));
type = forClassWithGenerics(CompletableFuture.class, responseEntity(String.class));
assertTrue(this.resultHandler.supports(handlerResult(value, type)));
// False
type = ResolvableType.forClass(String.class);
assertFalse(this.resultHandler.supports(handlerResult(value, type)));
type = ResolvableType.forClass(Completable.class);
assertFalse(this.resultHandler.supports(handlerResult(value, type)));
}
@Test
public void defaultOrder() throws Exception {
assertEquals(0, this.resultHandler.getOrder());
}
@Test
public void statusCode() throws Exception {
ResponseEntity<Void> value = ResponseEntity.noContent().build();
ResolvableType type = responseEntity(Void.class);
HandlerResult result = handlerResult(value, type);
this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5));
assertEquals(HttpStatus.NO_CONTENT, this.response.getStatusCode());
assertEquals(0, this.response.getHeaders().size());
assertNull(this.response.getBody());
}
@Test
public void headers() throws Exception {
URI location = new URI("/path");
ResolvableType type = responseEntity(Void.class);
ResponseEntity<Void> value = ResponseEntity.created(location).build();
HandlerResult result = handlerResult(value, type);
this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5));
assertEquals(HttpStatus.CREATED, this.response.getStatusCode());
assertEquals(1, this.response.getHeaders().size());
assertEquals(location, this.response.getHeaders().getLocation());
assertNull(this.response.getBody());
}
@Test
public void handleResponseEntityWithNullBody() throws Exception {
Object returnValue = Mono.just(notFound().build());
ResolvableType returnType = forClassWithGenerics(Mono.class, responseEntity(String.class));
HandlerResult result = handlerResult(returnValue, returnType);
this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5));
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
assertNull(this.response.getBody());
}
@Test
public void handleReturnTypes() throws Exception {
Object returnValue = ok("abc");
ResolvableType returnType = responseEntity(String.class);
testHandle(returnValue, returnType);
returnValue = Mono.just(ok("abc"));
returnType = forClassWithGenerics(Mono.class, responseEntity(String.class));
testHandle(returnValue, returnType);
returnValue = Mono.just(ok("abc"));
returnType = forClassWithGenerics(Single.class, responseEntity(String.class));
testHandle(returnValue, returnType);
returnValue = Mono.just(ok("abc"));
returnType = forClassWithGenerics(CompletableFuture.class, responseEntity(String.class));
testHandle(returnValue, returnType);
}
@Test
public void handleReturnValueLastModified() throws Exception {
Instant currentTime = Instant.now().truncatedTo(ChronoUnit.SECONDS);
Instant oneMinAgo = currentTime.minusSeconds(60);
this.request = MockServerHttpRequest.get("/path").ifModifiedSince(currentTime.toEpochMilli()).build();
ResponseEntity<String> entity = ok().lastModified(oneMinAgo.toEpochMilli()).body("body");
HandlerResult result = handlerResult(entity, responseEntity(String.class));
this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5));
assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, null, oneMinAgo);
}
@Test
public void handleReturnValueEtag() throws Exception {
String etagValue = "\"deadb33f8badf00d\"";
this.request = MockServerHttpRequest.get("/path").ifNoneMatch(etagValue).build();
ResponseEntity<String> entity = ok().eTag(etagValue).body("body");
HandlerResult result = handlerResult(entity, responseEntity(String.class));
this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5));
assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, etagValue, Instant.MIN);
}
@Test // SPR-14559
public void handleReturnValueEtagInvalidIfNoneMatch() throws Exception {
this.request = MockServerHttpRequest.get("/path").ifNoneMatch("unquoted").build();
ResponseEntity<String> entity = ok().eTag("\"deadb33f8badf00d\"").body("body");
HandlerResult result = handlerResult(entity, responseEntity(String.class));
this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5));
assertEquals(HttpStatus.OK, this.response.getStatusCode());
assertResponseBody("body");
}
@Test
public void handleReturnValueETagAndLastModified() throws Exception {
String eTag = "\"deadb33f8badf00d\"";
Instant currentTime = Instant.now().truncatedTo(ChronoUnit.SECONDS);
Instant oneMinAgo = currentTime.minusSeconds(60);
this.request = MockServerHttpRequest.get("/path")
.ifNoneMatch(eTag)
.ifModifiedSince(currentTime.toEpochMilli())
.build();
ResponseEntity<String> entity = ok().eTag(eTag).lastModified(oneMinAgo.toEpochMilli()).body("body");
HandlerResult result = handlerResult(entity, responseEntity(String.class));
this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5));
assertConditionalResponse(HttpStatus.NOT_MODIFIED, null, eTag, oneMinAgo);
}
@Test
public void handleReturnValueChangedETagAndLastModified() throws Exception {
String etag = "\"deadb33f8badf00d\"";
String newEtag = "\"changed-etag-value\"";
Instant currentTime = Instant.now().truncatedTo(ChronoUnit.SECONDS);
Instant oneMinAgo = currentTime.minusSeconds(60);
this.request = MockServerHttpRequest.get("/path")
.ifNoneMatch(etag)
.ifModifiedSince(currentTime.toEpochMilli())
.build();
ResponseEntity<String> entity = ok().eTag(newEtag).lastModified(oneMinAgo.toEpochMilli()).body("body");
HandlerResult result = handlerResult(entity, responseEntity(String.class));
this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5));
assertConditionalResponse(HttpStatus.OK, "body", newEtag, oneMinAgo);
}
@Test // SPR-14877
public void handleMonoWithWildcardBodyType() throws Exception {
ServerWebExchange exchange = createExchange();
exchange.getAttributes().put(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE,
Collections.singleton(MediaType.APPLICATION_JSON));
HandlerResult result = new HandlerResult(new TestController(), Mono.just(ok().body("body")),
ResolvableMethod.onClass(TestController.class)
.name("monoResponseEntityWildcard")
.resolveReturnType());
this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5));
assertEquals(HttpStatus.OK, this.response.getStatusCode());
assertResponseBody("\"body\"");
}
@Test // SPR-14877
public void handleMonoWithWildcardBodyTypeAndNullBody() throws Exception {
ServerWebExchange exchange = createExchange();
exchange.getAttributes().put(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE,
Collections.singleton(MediaType.APPLICATION_JSON));
HandlerResult result = new HandlerResult(new TestController(), Mono.just(notFound().build()),
ResolvableMethod.onClass(TestController.class)
.name("monoResponseEntityWildcard")
.resolveReturnType());
this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5));
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
assertNull(this.response.getBody());
}
private void testHandle(Object returnValue, ResolvableType type) {
initExchange();
HandlerResult result = handlerResult(returnValue, type);
this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5));
assertEquals(HttpStatus.OK, this.response.getStatusCode());
assertEquals("text/plain;charset=UTF-8", this.response.getHeaders().getFirst("Content-Type"));
assertResponseBody("abc");
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, this.response);
}
private ResolvableType responseEntity(Class<?> bodyType) {
return forClassWithGenerics(ResponseEntity.class, ResolvableType.forClass(bodyType));
}
private HandlerResult handlerResult(Object returnValue, ResolvableType type) {
MethodParameter param = ResolvableMethod.onClass(TestController.class).returning(type).resolveReturnType();
return new HandlerResult(new TestController(), returnValue, param);
}
private void assertResponseBody(String responseBody) {
StepVerifier.create(this.response.getBody())
.consumeNextWith(buf -> assertEquals(responseBody,
DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8)))
.expectComplete()
.verify();
}
private void assertConditionalResponse(HttpStatus status, String body, String etag, Instant lastModified) throws Exception {
assertEquals(status, this.response.getStatusCode());
if (body != null) {
assertResponseBody(body);
}
else {
assertNull(this.response.getBody());
}
if (etag != null) {
assertEquals(1, this.response.getHeaders().get(HttpHeaders.ETAG).size());
assertEquals(etag, this.response.getHeaders().getETag());
}
if (lastModified.isAfter(Instant.EPOCH)) {
assertEquals(1, this.response.getHeaders().get(HttpHeaders.LAST_MODIFIED).size());
assertEquals(lastModified.toEpochMilli(), this.response.getHeaders().getLastModified());
}
}
@SuppressWarnings("unused")
private static class TestController {
ResponseEntity<String> responseEntityString() { return null; }
ResponseEntity<Void> responseEntityVoid() { return null; }
Mono<ResponseEntity<String>> mono() { return null; }
Single<ResponseEntity<String>> single() { return null; }
CompletableFuture<ResponseEntity<String>> completableFuture() { return null; }
String string() { return null; }
Completable completable() { return null; }
Mono<ResponseEntity<?>> monoResponseEntityWildcard() { return null; }
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.web.reactive.result.method.annotation;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.ResolvableMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
/**
* Unit tests for {@link ServerWebExchangeArgumentResolver}.
* @author Rossen Stoyanchev
*/
public class ServerWebExchangeArgumentResolverTests {
private ServerWebExchangeArgumentResolver resolver = new ServerWebExchangeArgumentResolver();
private ServerWebExchange exchange;
private ResolvableMethod testMethod = ResolvableMethod.onClass(getClass()).name("handle");
@Before
public void setUp() throws Exception {
ServerHttpRequest request = MockServerHttpRequest.get("/path").build();
ServerHttpResponse response = new MockServerHttpResponse();
WebSessionManager sessionManager = new MockWebSessionManager(mock(WebSession.class));
this.exchange = new DefaultServerWebExchange(request, response, sessionManager);
}
@Test
public void supportsParameter() throws Exception {
assertTrue(this.resolver.supportsParameter(parameter(ServerWebExchange.class)));
assertTrue(this.resolver.supportsParameter(parameter(ServerHttpRequest.class)));
assertTrue(this.resolver.supportsParameter(parameter(ServerHttpResponse.class)));
assertTrue(this.resolver.supportsParameter(parameter(HttpMethod.class)));
assertFalse(this.resolver.supportsParameter(parameter(String.class)));
}
@Test
public void resolveArgument() throws Exception {
testResolveArgument(parameter(ServerWebExchange.class), this.exchange);
testResolveArgument(parameter(ServerHttpRequest.class), this.exchange.getRequest());
testResolveArgument(parameter(ServerHttpResponse.class), this.exchange.getResponse());
testResolveArgument(parameter(HttpMethod.class), HttpMethod.GET);
}
private void testResolveArgument(MethodParameter parameter, Object expected) {
Mono<Object> mono = this.resolver.resolveArgument(parameter, new BindingContext(), this.exchange);
assertSame(expected, mono.block());
}
private MethodParameter parameter(Class<?> parameterType) {
return this.testMethod.resolveParam(parameter -> parameterType.equals(parameter.getParameterType()));
}
@SuppressWarnings("unused")
public void handle(
ServerWebExchange exchange,
ServerHttpRequest request,
ServerHttpResponse response,
WebSession session,
HttpMethod httpMethod,
String s) {
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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.web.reactive.result.method.annotation;
import java.lang.reflect.Method;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link SessionAttributeMethodArgumentResolver}.
* @author Rossen Stoyanchev
*/
public class SessionAttributeMethodArgumentResolverTests {
private SessionAttributeMethodArgumentResolver resolver;
private ServerWebExchange exchange;
private WebSession session;
private Method handleMethod;
@Before
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
public void setUp() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();
this.resolver = new SessionAttributeMethodArgumentResolver(context.getBeanFactory());
this.session = mock(WebSession.class);
WebSessionManager sessionManager = new MockWebSessionManager(this.session);
ServerHttpRequest request = MockServerHttpRequest.get("/").build();
this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
this.handleMethod = ReflectionUtils.findMethod(getClass(), "handleWithSessionAttribute", (Class<?>[]) null);
}
@Test
public void supportsParameter() throws Exception {
assertTrue(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 0)));
assertFalse(this.resolver.supportsParameter(new MethodParameter(this.handleMethod, 4)));
}
@Test
public void resolve() throws Exception {
MethodParameter param = initMethodParameter(0);
Mono<Object> mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
StepVerifier.create(mono).expectError(ServerWebInputException.class).verify();
Foo foo = new Foo();
when(this.session.getAttribute("foo")).thenReturn(Optional.of(foo));
mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
assertSame(foo, mono.block());
}
@Test
public void resolveWithName() throws Exception {
MethodParameter param = initMethodParameter(1);
Foo foo = new Foo();
when(this.session.getAttribute("specialFoo")).thenReturn(Optional.of(foo));
Mono<Object> mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
assertSame(foo, mono.block());
}
@Test
public void resolveNotRequired() throws Exception {
MethodParameter param = initMethodParameter(2);
Mono<Object> mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
assertNull(mono.block());
Foo foo = new Foo();
when(this.session.getAttribute("foo")).thenReturn(Optional.of(foo));
mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
assertSame(foo, mono.block());
}
@Test
public void resolveOptional() throws Exception {
MethodParameter param = initMethodParameter(3);
Mono<Object> mono = this.resolver.resolveArgument(param, new BindingContext(), this.exchange);
assertNotNull(mono.block());
assertEquals(Optional.class, mono.block().getClass());
assertFalse(((Optional) mono.block()).isPresent());
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(new DefaultFormattingConversionService());
BindingContext bindingContext = new BindingContext(initializer);
Foo foo = new Foo();
when(this.session.getAttribute("foo")).thenReturn(Optional.of(foo));
mono = this.resolver.resolveArgument(param, bindingContext, this.exchange);
assertNotNull(mono.block());
assertEquals(Optional.class, mono.block().getClass());
Optional optional = (Optional) mono.block();
assertTrue(optional.isPresent());
assertSame(foo, optional.get());
}
private MethodParameter initMethodParameter(int parameterIndex) {
MethodParameter param = new SynthesizingMethodParameter(this.handleMethod, parameterIndex);
param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
GenericTypeResolver.resolveParameterType(param, this.resolver.getClass());
return param;
}
@SuppressWarnings({"unused", "OptionalUsedAsFieldOrParameterType"})
private void handleWithSessionAttribute(
@SessionAttribute Foo foo,
@SessionAttribute("specialFoo") Foo namedFoo,
@SessionAttribute(name="foo", required = false) Foo notRequiredFoo,
@SessionAttribute(name="foo") Optional<Foo> optionalFoo,
String notSupported) {
}
private static class Foo {
}
}

View File

@@ -0,0 +1,239 @@
/*
* 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.result.method.annotation;
import java.time.Duration;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.config.EnableWebReactive;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
/**
* @author Sebastien Deleuze
*/
public class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private AnnotationConfigApplicationContext wac;
private WebClient webClient;
@Override
@Before
public void setup() throws Exception {
super.setup();
this.webClient = WebClient.create("http://localhost:" + this.port + "/sse");
}
@Override
protected HttpHandler createHttpHandler() {
this.wac = new AnnotationConfigApplicationContext();
this.wac.register(TestConfiguration.class);
this.wac.refresh();
return WebHttpHandlerBuilder.webHandler(new DispatcherHandler(this.wac)).build();
}
@Test
public void sseAsString() throws Exception {
Flux<String> result = this.webClient.get()
.uri("/string")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.bodyToFlux(String.class));
StepVerifier.create(result)
.expectNext("foo 0")
.expectNext("foo 1")
.expectComplete()
.verify(Duration.ofSeconds(5L));
}
@Test
public void sseAsPerson() throws Exception {
Flux<Person> result = this.webClient.get()
.uri("/person")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.bodyToFlux(Person.class));
StepVerifier.create(result)
.expectNext(new Person("foo 0"))
.expectNext(new Person("foo 1"))
.expectComplete()
.verify(Duration.ofSeconds(5L));
}
@Test
public void sseAsEvent() throws Exception {
ResolvableType type = forClassWithGenerics(ServerSentEvent.class, String.class);
Flux<ServerSentEvent<String>> result = this.webClient.get()
.uri("/event")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.body(toFlux(type)));
StepVerifier.create(result)
.consumeNextWith( event -> {
assertEquals("0", event.id().get());
assertEquals("foo", event.data().get());
assertEquals("bar", event.comment().get());
assertFalse(event.event().isPresent());
assertFalse(event.retry().isPresent());
})
.consumeNextWith( event -> {
assertEquals("1", event.id().get());
assertEquals("foo", event.data().get());
assertEquals("bar", event.comment().get());
assertFalse(event.event().isPresent());
assertFalse(event.retry().isPresent());
})
.expectComplete()
.verify(Duration.ofSeconds(5L));
}
@Test
public void sseAsEventWithoutAcceptHeader() throws Exception {
Flux<ServerSentEvent<String>> result = this.webClient.get()
.uri("/event")
.accept(TEXT_EVENT_STREAM)
.exchange()
.flatMap(response -> response.body(toFlux(
forClassWithGenerics(ServerSentEvent.class, String.class))));
StepVerifier.create(result)
.consumeNextWith( event -> {
assertEquals("0", event.id().get());
assertEquals("foo", event.data().get());
assertEquals("bar", event.comment().get());
assertFalse(event.event().isPresent());
assertFalse(event.retry().isPresent());
})
.consumeNextWith( event -> {
assertEquals("1", event.id().get());
assertEquals("foo", event.data().get());
assertEquals("bar", event.comment().get());
assertFalse(event.event().isPresent());
assertFalse(event.retry().isPresent());
})
.expectComplete()
.verify(Duration.ofSeconds(5L));
}
@RestController
@SuppressWarnings("unused")
static class SseController {
@RequestMapping("/sse/string")
Flux<String> string() {
return Flux.interval(Duration.ofMillis(100)).map(l -> "foo " + l).take(2);
}
@RequestMapping("/sse/person")
Flux<Person> person() {
return Flux.interval(Duration.ofMillis(100)).map(l -> new Person("foo " + l)).take(2);
}
@RequestMapping("/sse/event")
Flux<ServerSentEvent<String>> sse() {
return Flux.interval(Duration.ofMillis(100)).map(l -> ServerSentEvent.builder("foo")
.id(Long.toString(l))
.comment("bar")
.build()).take(2);
}
}
@Configuration
@EnableWebReactive
@SuppressWarnings("unused")
static class TestConfiguration {
@Bean
public SseController sseController() {
return new SseController();
}
}
private static class Person {
private String name;
@SuppressWarnings("unused")
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Person person = (Person) o;
return !(this.name != null ? !this.name.equals(person.name) : person.name != null);
}
@Override
public int hashCode() {
return this.name != null ? this.name.hashCode() : 0;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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.result.view;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import reactor.test.StepVerifier;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.ModelMap;
import org.springframework.util.MimeType;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
/**
* Unit tests for {@link HttpMessageWriterView}.
* @author Rossen Stoyanchev
*/
public class HttpMessageWriterViewTests {
private HttpMessageWriterView view = new HttpMessageWriterView(new Jackson2JsonEncoder());
private ModelMap model = new ExtendedModelMap();
@Test
public void supportedMediaTypes() throws Exception {
List<MimeType> mimeTypes = Arrays.asList(
new MimeType("application", "json", StandardCharsets.UTF_8),
new MimeType("application", "*+json", StandardCharsets.UTF_8));
assertEquals(mimeTypes, this.view.getSupportedMediaTypes());
}
@Test
public void extractObject() throws Exception {
this.view.setModelKeys(Collections.singleton("foo2"));
this.model.addAttribute("foo1", "bar1");
this.model.addAttribute("foo2", "bar2");
this.model.addAttribute("foo3", "bar3");
assertEquals("bar2", this.view.extractObjectToRender(this.model));
}
@Test
public void extractObjectNoMatch() throws Exception {
this.view.setModelKeys(Collections.singleton("foo2"));
this.model.addAttribute("foo1", "bar1");
assertNull(this.view.extractObjectToRender(this.model));
}
@Test
public void extractObjectMultipleMatches() throws Exception {
this.view.setModelKeys(new HashSet<>(Arrays.asList("foo1", "foo2")));
this.model.addAttribute("foo1", "bar1");
this.model.addAttribute("foo2", "bar2");
this.model.addAttribute("foo3", "bar3");
Object value = this.view.extractObjectToRender(this.model);
assertNotNull(value);
assertEquals(HashMap.class, value.getClass());
Map<?, ?> map = (Map<?, ?>) value;
assertEquals(2, map.size());
assertEquals("bar1", map.get("foo1"));
assertEquals("bar2", map.get("foo2"));
}
@Test
public void extractObjectMultipleMatchesNotSupported() throws Exception {
HttpMessageWriterView view = new HttpMessageWriterView(new CharSequenceEncoder());
view.setModelKeys(new HashSet<>(Arrays.asList("foo1", "foo2")));
this.model.addAttribute("foo1", "bar1");
this.model.addAttribute("foo2", "bar2");
try {
view.extractObjectToRender(this.model);
fail();
}
catch (IllegalStateException ex) {
String message = ex.getMessage();
assertTrue(message, message.contains("Map rendering is not supported"));
}
}
@Test
public void extractObjectNotSupported() throws Exception {
HttpMessageWriterView view = new HttpMessageWriterView(new Jaxb2XmlEncoder());
view.setModelKeys(new HashSet<>(Collections.singletonList("foo1")));
this.model.addAttribute("foo1", "bar1");
try {
view.extractObjectToRender(this.model);
fail();
}
catch (IllegalStateException ex) {
String message = ex.getMessage();
assertTrue(message, message.contains("[foo1] is not supported"));
}
}
@Test
public void render() throws Exception {
Map<String, String> pojoData = new LinkedHashMap<>();
pojoData.put("foo", "f");
pojoData.put("bar", "b");
this.model.addAttribute("pojoData", pojoData);
this.view.setModelKeys(Collections.singleton("pojoData"));
MockServerHttpRequest request = MockServerHttpRequest.get("/path").build();
MockServerHttpResponse response = new MockServerHttpResponse();
WebSessionManager manager = new DefaultWebSessionManager();
ServerWebExchange exchange = new DefaultServerWebExchange(request, response, manager);
this.view.render(this.model, MediaType.APPLICATION_JSON, exchange).blockMillis(5000);
StepVerifier.create(response.getBody())
.consumeNextWith( buf -> assertEquals("{\"foo\":\"f\",\"bar\":\"b\"}",
DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8))
)
.expectComplete()
.verify();
}
@SuppressWarnings("unused")
private String handle() {
return null;
}
}

View File

@@ -0,0 +1,150 @@
/*
* 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.web.reactive.result.view;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests for redirect view, and query string construction.
* Doesn't test URL encoding, although it does check that it's called.
*
* @author Sebastien Deleuze
*/
public class RedirectViewTests {
private MockServerHttpRequest request;
private MockServerHttpResponse response;
@Before
public void setUp() {
this.request = MockServerHttpRequest.get("/").contextPath("/context").build();
this.response = new MockServerHttpResponse();
}
@Test(expected = IllegalArgumentException.class)
public void noUrlSet() throws Exception {
RedirectView rv = new RedirectView(null);
rv.afterPropertiesSet();
}
@Test
public void defaultStatusCode() {
String url = "http://url.somewhere.com";
RedirectView view = new RedirectView(url);
view.render(new HashMap<>(), MediaType.TEXT_HTML, createExchange());
assertEquals(HttpStatus.SEE_OTHER, this.response.getStatusCode());
assertEquals(URI.create(url), this.response.getHeaders().getLocation());
}
@Test
public void customStatusCode() {
String url = "http://url.somewhere.com";
RedirectView view = new RedirectView(url, HttpStatus.FOUND);
view.render(new HashMap<>(), MediaType.TEXT_HTML, createExchange());
assertEquals(HttpStatus.FOUND, this.response.getStatusCode());
assertEquals(URI.create(url), this.response.getHeaders().getLocation());
}
@Test
public void contextRelative() {
String url = "/test.html";
RedirectView view = new RedirectView(url);
view.render(new HashMap<>(), MediaType.TEXT_HTML, createExchange());
assertEquals(URI.create("/context/test.html"), this.response.getHeaders().getLocation());
}
@Test
public void contextRelativeQueryParam() {
String url = "/test.html?id=1";
RedirectView view = new RedirectView(url);
view.render(new HashMap<>(), MediaType.TEXT_HTML, createExchange());
assertEquals(URI.create("/context/test.html?id=1"), this.response.getHeaders().getLocation());
}
@Test
public void remoteHost() {
RedirectView view = new RedirectView("");
assertFalse(view.isRemoteHost("http://url.somewhere.com"));
assertFalse(view.isRemoteHost("/path"));
assertFalse(view.isRemoteHost("http://url.somewhereelse.com"));
view.setHosts("url.somewhere.com");
assertFalse(view.isRemoteHost("http://url.somewhere.com"));
assertFalse(view.isRemoteHost("/path"));
assertTrue(view.isRemoteHost("http://url.somewhereelse.com"));
}
@Test
public void expandUriTemplateVariablesFromModel() {
String url = "http://url.somewhere.com?foo={foo}";
Map<String, String> model = Collections.singletonMap("foo", "bar");
RedirectView view = new RedirectView(url);
view.render(model, MediaType.TEXT_HTML, createExchange());
assertEquals(URI.create("http://url.somewhere.com?foo=bar"), this.response.getHeaders().getLocation());
}
@Test
public void expandUriTemplateVariablesFromExchangeAttribute() {
String url = "http://url.somewhere.com?foo={foo}";
Map<String, String> attributes = Collections.singletonMap("foo", "bar");
DefaultServerWebExchange exchange = createExchange();
exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, attributes);
RedirectView view = new RedirectView(url);
view.render(new HashMap<>(), MediaType.TEXT_HTML, exchange);
assertEquals(URI.create("http://url.somewhere.com?foo=bar"), this.response.getHeaders().getLocation());
}
@Test
public void propagateQueryParams() throws Exception {
RedirectView view = new RedirectView("http://url.somewhere.com?foo=bar#bazz");
view.setPropagateQuery(true);
this.request = MockServerHttpRequest.get("http://url.somewhere.com?a=b&c=d").build();
view.render(new HashMap<>(), MediaType.TEXT_HTML, createExchange());
assertEquals(HttpStatus.SEE_OTHER, this.response.getStatusCode());
assertEquals(URI.create("http://url.somewhere.com?foo=bar&a=b&c=d#bazz"),
this.response.getHeaders().getLocation());
}
@NotNull
private DefaultServerWebExchange createExchange() {
return new DefaultServerWebExchange(this.request, this.response);
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.web.reactive.result.view;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link RequestContext}.
* @author Rossen Stoyanchev
*/
public class RequestContextTests {
private ServerWebExchange exchange;
private GenericApplicationContext applicationContext;
private Map<String, Object> model = new HashMap<>();
@Before
public void init() {
MockServerHttpRequest request = MockServerHttpRequest.get("/").contextPath("foo/").build();
MockServerHttpResponse response = new MockServerHttpResponse();
this.exchange = new DefaultServerWebExchange(request, response);
this.applicationContext = new GenericApplicationContext();
this.applicationContext.refresh();
}
@Test
public void testGetContextUrl() throws Exception {
RequestContext context = new RequestContext(this.exchange, this.model, this.applicationContext);
assertEquals("foo/bar", context.getContextUrl("bar"));
}
@Test
public void testGetContextUrlWithMap() throws Exception {
RequestContext context = new RequestContext(this.exchange, this.model, this.applicationContext);
Map<String, Object> map = new HashMap<>();
map.put("foo", "bar");
map.put("spam", "bucket");
assertEquals("foo/bar?spam=bucket", context.getContextUrl("{foo}?spam={spam}", map));
}
@Test
public void testGetContextUrlWithMapEscaping() throws Exception {
RequestContext context = new RequestContext(this.exchange, this.model, this.applicationContext);
Map<String, Object> map = new HashMap<>();
map.put("foo", "bar baz");
map.put("spam", "&bucket=");
assertEquals("foo/bar%20baz?spam=%26bucket%3D", context.getContextUrl("{foo}?spam={spam}", map));
}
}

Some files were not shown because too many files have changed in this diff Show More