org.springframework.web.server.adapter.WebHttpHandlerBuilder Maven / Gradle / Ivy
/*
* 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.Collections;
import java.util.List;
import java.util.function.Consumer;
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.lang.Nullable;
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.i18n.LocaleContextResolver;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
/**
* This builder has two purposes.
*
* 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.
*
*
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}.
*
*
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";
/** Well-known name for the LocaleContextResolver in the bean factory. */
public static final String LOCALE_CONTEXT_RESOLVER_BEAN_NAME = "localeContextResolver";
private final WebHandler webHandler;
private final List filters = new ArrayList<>();
private final List exceptionHandlers = new ArrayList<>();
@Nullable
private WebSessionManager sessionManager;
@Nullable
private ServerCodecConfigurer codecConfigurer;
@Nullable
private LocaleContextResolver localeContextResolver;
@Nullable
private ApplicationContext applicationContext;
/**
* Private constructor.
*/
private WebHttpHandlerBuilder(WebHandler webHandler) {
Assert.notNull(webHandler, "WebHandler must not be null");
this.webHandler = webHandler;
}
/**
* Private constructor to use when initialized from an ApplicationContext.
*/
private WebHttpHandlerBuilder(WebHandler webHandler, ApplicationContext applicationContext) {
Assert.notNull(webHandler, "WebHandler must not be null");
this.webHandler = webHandler;
this.applicationContext = applicationContext;
}
/**
* Copy constructor.
*/
private WebHttpHandlerBuilder(WebHttpHandlerBuilder other) {
this.webHandler = other.webHandler;
this.filters.addAll(other.filters);
this.exceptionHandlers.addAll(other.exceptionHandlers);
this.sessionManager = other.sessionManager;
this.codecConfigurer = other.codecConfigurer;
this.localeContextResolver = other.localeContextResolver;
}
/**
* 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:
*
* - {@link WebHandler} [1] -- looked up by the name
* {@link #WEB_HANDLER_BEAN_NAME}.
*
- {@link WebFilter} [0..N] -- detected by type and ordered,
* see {@link AnnotationAwareOrderComparator}.
*
- {@link WebExceptionHandler} [0..N] -- detected by type and
* ordered.
*
- {@link WebSessionManager} [0..1] -- looked up by the name
* {@link #WEB_SESSION_MANAGER_BEAN_NAME}.
*
- {@link ServerCodecConfigurer} [0..1] -- looked up by the name
* {@link #SERVER_CODEC_CONFIGURER_BEAN_NAME}.
*
- {@link LocaleContextResolver} [0..1] -- looked up by the name
* {@link #LOCALE_CONTEXT_RESOLVER_BEAN_NAME}.
*
* @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), context);
// Autowire lists for @Bean + @Order
SortedBeanContainer container = new SortedBeanContainer();
context.getAutowireCapableBeanFactory().autowireBean(container);
builder.filters(filters -> filters.addAll(container.getFilters()));
builder.exceptionHandlers(handlers -> handlers.addAll(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
}
try {
builder.localeContextResolver(
context.getBean(LOCALE_CONTEXT_RESOLVER_BEAN_NAME, LocaleContextResolver.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;
}
/**
* Manipulate the "live" list of currently configured filters.
* @param consumer the consumer to use
*/
public WebHttpHandlerBuilder filters(Consumer> consumer) {
consumer.accept(this.filters);
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;
}
/**
* Manipulate the "live" list of currently configured exception handlers.
* @param consumer the consumer to use
*/
public WebHttpHandlerBuilder exceptionHandlers(Consumer> consumer) {
consumer.accept(this.exceptionHandlers);
return this;
}
/**
* Configure the {@link WebSessionManager} to set on the
* {@link ServerWebExchange WebServerExchange}.
* 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;
}
/**
* Configure the {@link LocaleContextResolver} to set on the
* {@link ServerWebExchange WebServerExchange}.
* @param localeContextResolver the locale context resolver
*/
public WebHttpHandlerBuilder localeContextResolver(LocaleContextResolver localeContextResolver) {
this.localeContextResolver = localeContextResolver;
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);
}
if (this.localeContextResolver != null) {
adapted.setLocaleContextResolver(this.localeContextResolver);
}
if (this.applicationContext != null) {
adapted.setApplicationContext(this.applicationContext);
}
return adapted;
}
/**
* Clone this {@link WebHttpHandlerBuilder}.
* @return the cloned builder instance
*/
@Override
public WebHttpHandlerBuilder clone() {
return new WebHttpHandlerBuilder(this);
}
private static class SortedBeanContainer {
private List filters = Collections.emptyList();
private List exceptionHandlers = Collections.emptyList();
@Autowired(required = false)
public void setFilters(List filters) {
this.filters = filters;
}
public List getFilters() {
return this.filters;
}
@Autowired(required = false)
public void setExceptionHandlers(List exceptionHandlers) {
this.exceptionHandlers = exceptionHandlers;
}
public List getExceptionHandlers() {
return this.exceptionHandlers;
}
}
}