Rename spring-web-reactive to spring-webflux
Issue: SPR-15190
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user