/* * 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.server.adapter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebHandler; import org.springframework.web.server.handler.ExceptionHandlingWebHandler; import org.springframework.web.server.handler.FilteringWebHandler; import org.springframework.web.server.session.DefaultWebSessionManager; import org.springframework.web.server.session.WebSessionManager; /** * This builder has two purposes. * * <p>One is to assemble a processing chain that consists of a target * {@link WebHandler}, then decorated with a set of {@link WebFilter}'s, then * further decorated with a set of {@link WebExceptionHandler}'s. * * <p>The second purpose is to adapt the resulting processing chain to an * {@link HttpHandler} -- the lowest level reactive HTTP handling abstraction, * which can then be used with any of the supported runtimes. The adaptation * is done with the help of {@link HttpWebHandlerAdapter}. * * <p>The processing chain can be assembled manually via builder methods, or * detected from Spring configuration via * {@link #applicationContext(ApplicationContext)}, or a mix of both. * * @author Rossen Stoyanchev * @author Sebastien Deleuze * @since 5.0 * @see HttpWebHandlerAdapter */ public class WebHttpHandlerBuilder { /** Well-known name for the target WebHandler in the bean factory. */ public static final String WEB_HANDLER_BEAN_NAME = "webHandler"; /** Well-known name for the WebSessionManager in the bean factory. */ public static final String WEB_SESSION_MANAGER_BEAN_NAME = "webSessionManager"; /** Well-known name for the ServerCodecConfigurer in the bean factory. */ public static final String SERVER_CODEC_CONFIGURER_BEAN_NAME = "serverCodecConfigurer"; private final WebHandler webHandler; private final List<WebFilter> filters = new ArrayList<>(); private final List<WebExceptionHandler> exceptionHandlers = new ArrayList<>(); private WebSessionManager sessionManager; private ServerCodecConfigurer codecConfigurer; /** * Private constructor. */ private WebHttpHandlerBuilder(WebHandler webHandler) { Assert.notNull(webHandler, "WebHandler must not be null"); this.webHandler = webHandler; } /** * Static factory method to create a new builder instance. * @param webHandler the target handler for the request * @return the prepared builder */ public static WebHttpHandlerBuilder webHandler(WebHandler webHandler) { return new WebHttpHandlerBuilder(webHandler); } /** * Static factory method to create a new builder instance by detecting beans * in an {@link ApplicationContext}. The following are detected: * <ul> * <li>{@link WebHandler} [1] -- looked up by the name * {@link #WEB_HANDLER_BEAN_NAME}. * <li>{@link WebFilter} [0..N] -- detected by type and ordered, * see {@link AnnotationAwareOrderComparator}. * <li>{@link WebExceptionHandler} [0..N] -- detected by type and * ordered. * <li>{@link WebSessionManager} [0..1] -- looked up by the name * {@link #WEB_SESSION_MANAGER_BEAN_NAME}. * <li>{@link ServerCodecConfigurer} [0..1] -- looked up by the name * {@link #SERVER_CODEC_CONFIGURER_BEAN_NAME}. * </ul> * @param context the application context to use for the lookup * @return the prepared builder */ public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) { WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder( context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class)); // Autowire lists for @Bean + @Order SortedBeanContainer container = new SortedBeanContainer(); context.getAutowireCapableBeanFactory().autowireBean(container); builder.filters(container.getFilters()); builder.exceptionHandlers(container.getExceptionHandlers()); try { builder.sessionManager( context.getBean(WEB_SESSION_MANAGER_BEAN_NAME, WebSessionManager.class)); } catch (NoSuchBeanDefinitionException ex) { // Fall back on default } try { builder.codecConfigurer( context.getBean(SERVER_CODEC_CONFIGURER_BEAN_NAME, ServerCodecConfigurer.class)); } catch (NoSuchBeanDefinitionException ex) { // Fall back on default } return builder; } /** * Add the given filter(s). * @param filters the filter(s) to add that's */ public WebHttpHandlerBuilder filter(WebFilter... filters) { if (!ObjectUtils.isEmpty(filters)) { this.filters.addAll(Arrays.asList(filters)); } return this; } /** * Add the given filters. * @param filters the filters to add */ public WebHttpHandlerBuilder filters(List<? extends WebFilter> filters) { if (!ObjectUtils.isEmpty(filters)) { this.filters.addAll(filters); } return this; } /** * Insert the given filter before other configured filters. * @param filter the filters to insert */ public WebHttpHandlerBuilder prependFilter(WebFilter filter) { Assert.notNull(filter, "WebFilter is required"); this.filters.add(0, filter); return this; } /** * Add the given exception handler(s). * @param handlers the exception handler(s) */ public WebHttpHandlerBuilder exceptionHandler(WebExceptionHandler... handlers) { if (!ObjectUtils.isEmpty(handlers)) { this.exceptionHandlers.addAll(Arrays.asList(handlers)); } return this; } /** * Add the given exception handlers. * @param handlers the exception handlers */ public WebHttpHandlerBuilder exceptionHandlers(List<WebExceptionHandler> handlers) { if (!ObjectUtils.isEmpty(handlers)) { this.exceptionHandlers.addAll(handlers); } return this; } /** * Insert the given exception handler before other configured handlers. * @param handler the exception handler to insert */ public WebHttpHandlerBuilder prependExceptionHandler(WebExceptionHandler handler) { Assert.notNull(handler, "WebExceptionHandler is required"); this.exceptionHandlers.add(0, handler); return this; } /** * Configure the {@link WebSessionManager} to set on the * {@link ServerWebExchange WebServerExchange}. * <p>By default {@link DefaultWebSessionManager} is used. * @param manager the session manager * @see HttpWebHandlerAdapter#setSessionManager(WebSessionManager) */ public WebHttpHandlerBuilder sessionManager(WebSessionManager manager) { this.sessionManager = manager; return this; } /** * Configure the {@link ServerCodecConfigurer} to set on the * {@link ServerWebExchange WebServerExchange}. * @param codecConfigurer the codec configurer */ public WebHttpHandlerBuilder codecConfigurer(ServerCodecConfigurer codecConfigurer) { this.codecConfigurer = codecConfigurer; return this; } /** * Build the {@link HttpHandler}. */ public HttpHandler build() { WebHandler decorated; decorated = new FilteringWebHandler(this.webHandler, this.filters); decorated = new ExceptionHandlingWebHandler(decorated, this.exceptionHandlers); HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated); if (this.sessionManager != null) { adapted.setSessionManager(this.sessionManager); } if (this.codecConfigurer != null) { adapted.setCodecConfigurer(this.codecConfigurer); } return adapted; } private static class SortedBeanContainer { private List<WebFilter> filters; private List<WebExceptionHandler> exceptionHandlers; @Autowired(required = false) public void setFilters(List<WebFilter> filters) { this.filters = filters; } public List<WebFilter> getFilters() { return this.filters; } @Autowired(required = false) public void setExceptionHandlers(List<WebExceptionHandler> exceptionHandlers) { this.exceptionHandlers = exceptionHandlers; } public List<WebExceptionHandler> getExceptionHandlers() { return this.exceptionHandlers; } } }