/* * 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.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; 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.reactive.HttpHandler; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebHandler; import org.springframework.web.server.adapter.HttpWebHandlerAdapter; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; /** * Central dispatcher for HTTP request handlers/controllers. Dispatches to registered * handlers for processing a web request, providing convenient mapping facilities. * * <p>It can use any {@link HandlerMapping} implementation to control the routing of * requests to handler objects. HandlerMapping objects can be defined as beans in * the application context. * * <p>It can use any {@link HandlerAdapter}; this allows for using any handler interface. * HandlerAdapter objects can be added as beans in the application context. * * <p>It can use any {@link HandlerResultHandler}; this allows to process the result of * the request handling. HandlerResultHandler objects can be added as beans in the * application context. * * @author Rossen Stoyanchev * @author Sebastien Deleuze * @author Juergen Hoeller * @since 5.0 */ public class DispatcherHandler implements WebHandler, ApplicationContextAware { @SuppressWarnings("ThrowableInstanceNeverThrown") private static final Exception HANDLER_NOT_FOUND_EXCEPTION = new ResponseStatusException(HttpStatus.NOT_FOUND, "No matching handler"); private static final Log logger = LogFactory.getLog(DispatcherHandler.class); private List<HandlerMapping> handlerMappings; private List<HandlerAdapter> handlerAdapters; private List<HandlerResultHandler> resultHandlers; /** * Create a new {@code DispatcherHandler} which needs to be configured with * an {@link ApplicationContext} through {@link #setApplicationContext}. */ public DispatcherHandler() { } /** * Create a new {@code DispatcherHandler} for the given {@link ApplicationContext}. * @param applicationContext the application context to find the handler beans in */ public DispatcherHandler(ApplicationContext applicationContext) { initStrategies(applicationContext); } @Override public void setApplicationContext(ApplicationContext applicationContext) { initStrategies(applicationContext); } protected void initStrategies(ApplicationContext context) { Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( context, HandlerMapping.class, true, false); this.handlerMappings = new ArrayList<>(mappingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings); Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( context, HandlerAdapter.class, true, false); this.handlerAdapters = new ArrayList<>(adapterBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerAdapters); Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors( context, HandlerResultHandler.class, true, false); this.resultHandlers = new ArrayList<>(beans.values()); AnnotationAwareOrderComparator.sort(this.resultHandlers); } @Override public Mono<Void> handle(ServerWebExchange exchange) { if (logger.isDebugEnabled()) { ServerHttpRequest request = exchange.getRequest(); logger.debug("Processing " + request.getMethod() + " request for [" + request.getURI() + "]"); } return Flux.fromIterable(this.handlerMappings) .concatMap(mapping -> mapping.getHandler(exchange)) .next() .switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION)) .flatMap(handler -> invokeHandler(exchange, handler)) .flatMap(result -> handleResult(exchange, result)); } private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) { for (HandlerAdapter handlerAdapter : this.handlerAdapters) { if (handlerAdapter.supports(handler)) { return handlerAdapter.handle(exchange, handler); } } return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler)); } private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) { return getResultHandler(result).handleResult(exchange, result) .onErrorResume(ex -> result.applyExceptionHandler(ex).flatMap(exceptionResult -> getResultHandler(exceptionResult).handleResult(exchange, exceptionResult))); } private HandlerResultHandler getResultHandler(HandlerResult handlerResult) { for (HandlerResultHandler resultHandler : this.resultHandlers) { if (resultHandler.supports(handlerResult)) { return resultHandler; } } throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue()); } /** * Expose a dispatcher-based {@link WebHandler} for the given application context, * typically for further configuration with filters and exception handlers through * a {@link org.springframework.web.server.adapter.WebHttpHandlerBuilder}. * @param applicationContext the application context to find the handler beans in * @see #DispatcherHandler(ApplicationContext) * @see org.springframework.web.server.adapter.WebHttpHandlerBuilder#webHandler */ public static WebHandler toWebHandler(ApplicationContext applicationContext) { return new DispatcherHandler(applicationContext); } /** * Expose a dispatcher-based {@link HttpHandler} for the given application context, * typically for direct registration with an engine adapter such as * {@link org.springframework.http.server.reactive.ServletHttpHandlerAdapter}. * * <p>Delegates to {@link WebHttpHandlerBuilder#applicationContext} that * detects the target {@link DispatcherHandler} along with * {@link org.springframework.web.server.WebFilter}s, and * {@link org.springframework.web.server.WebExceptionHandler}s in the given * ApplicationContext. * * @param context the application context to find the handler beans in * @see #DispatcherHandler(ApplicationContext) * @see HttpWebHandlerAdapter * @see org.springframework.http.server.reactive.ServletHttpHandlerAdapter * @see org.springframework.http.server.reactive.ReactorHttpHandlerAdapter * @see org.springframework.http.server.reactive.UndertowHttpHandlerAdapter */ public static HttpHandler toHttpHandler(ApplicationContext context) { return WebHttpHandlerBuilder.applicationContext(context).build(); } }