diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/DispatcherHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/DispatcherHandler.java index 3070db7f275..917a1adb151 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/DispatcherHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/DispatcherHandler.java @@ -19,6 +19,7 @@ package org.springframework.web.reactive; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -30,7 +31,6 @@ import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.http.HttpStatus; import org.springframework.http.server.ReactiveHttpHandler; import org.springframework.http.server.ReactiveServerHttpRequest; import org.springframework.http.server.ReactiveServerHttpResponse; @@ -76,6 +76,7 @@ public class DispatcherHandler implements ReactiveHttpHandler, ApplicationContex context, HandlerMapping.class, true, false); this.handlerMappings = new ArrayList<>(mappingBeans.values()); + this.handlerMappings.add(new NotFoundHandlerMapping()); AnnotationAwareOrderComparator.sort(this.handlerMappings); Map adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( @@ -98,13 +99,9 @@ public class DispatcherHandler implements ReactiveHttpHandler, ApplicationContex logger.debug("Processing " + request.getMethod() + " request for [" + request.getURI() + "]"); } - Publisher handlerPublisher = getHandler(request); - if (handlerPublisher == null) { - // No exception handling mechanism yet - response.setStatusCode(HttpStatus.NOT_FOUND); - response.writeHeaders(); - return Publishers.empty(); - } + Publisher mappings = Publishers.from(this.handlerMappings); + Publisher handlerPublisher = Publishers.concatMap(mappings, m -> m.getHandler(request)); + handlerPublisher = first(handlerPublisher); Publisher resultPublisher = Publishers.concatMap(handlerPublisher, handler -> { HandlerAdapter handlerAdapter = getHandlerAdapter(handler); @@ -117,16 +114,6 @@ public class DispatcherHandler implements ReactiveHttpHandler, ApplicationContex }); } - protected Publisher getHandler(ReactiveServerHttpRequest request) { - for (HandlerMapping handlerMapping : this.handlerMappings) { - Publisher handlerPublisher = handlerMapping.getHandler(request); - if (handlerPublisher != null) { - return handlerPublisher; - } - } - return null; - } - protected HandlerAdapter getHandlerAdapter(Object handler) { for (HandlerAdapter handlerAdapter : this.handlerAdapters) { if (handlerAdapter.supports(handler)) { @@ -145,4 +132,21 @@ public class DispatcherHandler implements ReactiveHttpHandler, ApplicationContex throw new IllegalStateException("No HandlerResultHandler: " + handlerResult.getValue()); } -} + + private static Publisher first(Publisher source) { + return Publishers.lift(source, (e, subscriber) -> { + subscriber.onNext(e); + subscriber.onComplete(); + }); + } + + private static class NotFoundHandlerMapping implements HandlerMapping { + + @Override + public Publisher getHandler(ReactiveServerHttpRequest request) { + return Publishers.error(new HandlerNotFoundException(request.getMethod(), + request.getURI().getPath(), request.getHeaders())); + } + } + +} \ No newline at end of file diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerNotFoundException.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerNotFoundException.java new file mode 100644 index 00000000000..d8bc40f933f --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/HandlerNotFoundException.java @@ -0,0 +1,59 @@ +/* + * 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; + +import org.springframework.core.NestedRuntimeException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +/** + * @author Rossen Stoyanchev + */ +public class HandlerNotFoundException extends NestedRuntimeException { + + private final HttpMethod method; + + private final String requestURL; + + private final HttpHeaders headers; + + + /** + * Constructor for NoHandlerFoundException. + * @param method the HTTP method + * @param requestURL the HTTP request URL + * @param headers the HTTP request headers + */ + public HandlerNotFoundException(HttpMethod method, String requestURL, HttpHeaders headers) { + super("No handler found for " + method + " " + requestURL); + this.method = method; + this.requestURL = requestURL; + this.headers = headers; + } + + + public HttpMethod getMethod() { + return this.method; + } + + public String getRequestURL() { + return this.requestURL; + } + + public HttpHeaders getHeaders() { + return this.headers; + } +} diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMapping.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMapping.java index 1eac42a113a..986c7498ef5 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMapping.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMapping.java @@ -21,6 +21,7 @@ import java.util.Map; import org.reactivestreams.Publisher; import reactor.Publishers; +import reactor.core.publisher.PublisherFactory; import org.springframework.http.server.ReactiveServerHttpRequest; import org.springframework.web.reactive.HandlerMapping; @@ -43,9 +44,14 @@ public class SimpleUrlHandlerMapping implements HandlerMapping { @Override public Publisher getHandler(ReactiveServerHttpRequest request) { - String path = request.getURI().getPath(); - Object handler = this.handlerMap.get(path); - return (handler != null ? Publishers.just(handler) : null); + return PublisherFactory.create(subscriber -> { + String path = request.getURI().getPath(); + Object handler = this.handlerMap.get(path); + if (handler != null) { + subscriber.onNext(handler); + } + subscriber.onComplete(); + }); } } diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/RequestMappingHandlerMapping.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/RequestMappingHandlerMapping.java index ed9670b3e8c..86e87f70727 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/RequestMappingHandlerMapping.java @@ -28,19 +28,19 @@ import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; -import reactor.Publishers; +import reactor.core.publisher.PublisherFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.server.ReactiveServerHttpRequest; -import org.springframework.web.reactive.HandlerMapping; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethodSelector; +import org.springframework.web.reactive.HandlerMapping; /** @@ -95,18 +95,21 @@ public class RequestMappingHandlerMapping implements HandlerMapping, @Override public Publisher getHandler(ReactiveServerHttpRequest request) { - for (Map.Entry entry : this.methodMap.entrySet()) { - RequestMappingInfo info = entry.getKey(); - if (info.matchesRequest(request)) { - HandlerMethod handlerMethod = entry.getValue(); - if (logger.isDebugEnabled()) { - logger.debug("Mapped " + request.getMethod() + " " + - request.getURI().getPath() + " to [" + handlerMethod + "]"); + return PublisherFactory.create(subscriber -> { + for (Map.Entry entry : this.methodMap.entrySet()) { + RequestMappingInfo info = entry.getKey(); + if (info.matchesRequest(request)) { + HandlerMethod handlerMethod = entry.getValue(); + if (logger.isDebugEnabled()) { + logger.debug("Mapped " + request.getMethod() + " " + + request.getURI().getPath() + " to [" + handlerMethod + "]"); + } + subscriber.onNext(handlerMethod); + break; } - return Publishers.just(handlerMethod); } - } - return null; + subscriber.onComplete(); + }); } diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java index 94bf276d1d4..73427f772a7 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java @@ -21,6 +21,7 @@ import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; +import org.junit.Ignore; import org.junit.Test; import org.reactivestreams.Publisher; import reactor.io.buffer.Buffer; @@ -31,11 +32,14 @@ import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.http.server.ReactiveServerHttpRequest; import org.springframework.http.server.ReactiveServerHttpResponse; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClientException; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.http.server.AbstractHttpHandlerIntegrationTests; import org.springframework.http.server.ReactiveHttpHandler; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.reactive.method.annotation.RequestMappingHandlerMapping; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -89,6 +93,24 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler assertArrayEquals("bar".getBytes(UTF_8), response.getBody()); } + // TODO: remove @Ignore after 404 default handling + + @Test + @Ignore + public void testNotFound() throws Exception { + + RestTemplate restTemplate = new RestTemplate(); + + URI url = new URI("http://localhost:" + port + "/oops"); + RequestEntity request = RequestEntity.get(url).build(); + try { + restTemplate.exchange(request, byte[].class); + } + catch (HttpClientErrorException ex) { + assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode()); + } + } + private static class TestHandlerMapping extends SimpleUrlHandlerMapping {