/*
* Copyright 2002-2018 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 java.util.function.Consumer;
import java.util.stream.Collectors;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
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 WebHandler
, then decorated with a set of WebFilters
, then further decorated with a set of WebExceptionHandlers
.
The second purpose is to adapt the resulting processing chain to an 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 HttpWebHandlerAdapter
.
The processing chain can be assembled manually via builder methods, or detected from a Spring ApplicationContext
via applicationContext
, or a mix of both.
Author: Rossen Stoyanchev, Sebastien Deleuze See Also: Since: 5.0
/**
* 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 WebFilters}, then further decorated with
* a set of {@link WebExceptionHandler WebExceptionHandlers}.
*
* <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
* a Spring {@link ApplicationContext} via {@link #applicationContext}, or a mix of both.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
* @since 5.0
* @see HttpWebHandlerAdapter
*/
public final class WebHttpHandlerBuilder {
Well-known name for the target WebHandler in the bean factory. /** 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. /** 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. /** 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. /** Well-known name for the LocaleContextResolver in the bean factory. */
public static final String LOCALE_CONTEXT_RESOLVER_BEAN_NAME = "localeContextResolver";
Well-known name for the ForwardedHeaderTransformer in the bean factory. /** Well-known name for the ForwardedHeaderTransformer in the bean factory. */
public static final String FORWARDED_HEADER_TRANSFORMER_BEAN_NAME = "forwardedHeaderTransformer";
private final WebHandler webHandler;
@Nullable
private final ApplicationContext applicationContext;
private final List<WebFilter> filters = new ArrayList<>();
private final List<WebExceptionHandler> exceptionHandlers = new ArrayList<>();
@Nullable
private WebSessionManager sessionManager;
@Nullable
private ServerCodecConfigurer codecConfigurer;
@Nullable
private LocaleContextResolver localeContextResolver;
@Nullable
private ForwardedHeaderTransformer forwardedHeaderTransformer;
Private constructor to use when initialized from an ApplicationContext.
/**
* Private constructor to use when initialized from an ApplicationContext.
*/
private WebHttpHandlerBuilder(WebHandler webHandler, @Nullable ApplicationContext applicationContext) {
Assert.notNull(webHandler, "WebHandler must not be null");
this.webHandler = webHandler;
this.applicationContext = applicationContext;
}
Copy constructor.
/**
* Copy constructor.
*/
private WebHttpHandlerBuilder(WebHttpHandlerBuilder other) {
this.webHandler = other.webHandler;
this.applicationContext = other.applicationContext;
this.filters.addAll(other.filters);
this.exceptionHandlers.addAll(other.exceptionHandlers);
this.sessionManager = other.sessionManager;
this.codecConfigurer = other.codecConfigurer;
this.localeContextResolver = other.localeContextResolver;
this.forwardedHeaderTransformer = other.forwardedHeaderTransformer;
}
Static factory method to create a new builder instance.
Params: - webHandler – the target handler for the request
Returns: the prepared builder
/**
* 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, null);
}
Static factory method to create a new builder instance by detecting beans in an ApplicationContext
. The following are detected:
WebHandler
[1] -- looked up by the name WEB_HANDLER_BEAN_NAME
. WebFilter
[0..N] -- detected by type and ordered, see AnnotationAwareOrderComparator
. WebExceptionHandler
[0..N] -- detected by type and ordered. WebSessionManager
[0..1] -- looked up by the name WEB_SESSION_MANAGER_BEAN_NAME
. ServerCodecConfigurer
[0..1] -- looked up by the name SERVER_CODEC_CONFIGURER_BEAN_NAME
. LocaleContextResolver
[0..1] -- looked up by the name LOCALE_CONTEXT_RESOLVER_BEAN_NAME
.
Params: - context – the application context to use for the lookup
Returns: the prepared builder
/**
* 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}.
* <li>{@link LocaleContextResolver} [0..1] -- looked up by the name
* {@link #LOCALE_CONTEXT_RESOLVER_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), context);
List<WebFilter> webFilters = context
.getBeanProvider(WebFilter.class)
.orderedStream()
.collect(Collectors.toList());
builder.filters(filters -> filters.addAll(webFilters));
List<WebExceptionHandler> exceptionHandlers = context
.getBeanProvider(WebExceptionHandler.class)
.orderedStream()
.collect(Collectors.toList());
builder.exceptionHandlers(handlers -> handlers.addAll(exceptionHandlers));
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
}
try {
builder.localeContextResolver(
context.getBean(LOCALE_CONTEXT_RESOLVER_BEAN_NAME, LocaleContextResolver.class));
}
catch (NoSuchBeanDefinitionException ex) {
// Fall back on default
}
try {
builder.forwardedHeaderTransformer(
context.getBean(FORWARDED_HEADER_TRANSFORMER_BEAN_NAME, ForwardedHeaderTransformer.class));
}
catch (NoSuchBeanDefinitionException ex) {
// Fall back on default
}
return builder;
}
Add the given filter(s).
Params: - filters – the filter(s) to add that's
/**
* 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));
updateFilters();
}
return this;
}
Manipulate the "live" list of currently configured filters.
Params: - consumer – the consumer to use
/**
* Manipulate the "live" list of currently configured filters.
* @param consumer the consumer to use
*/
public WebHttpHandlerBuilder filters(Consumer<List<WebFilter>> consumer) {
consumer.accept(this.filters);
updateFilters();
return this;
}
private void updateFilters() {
if (this.filters.isEmpty()) {
return;
}
List<WebFilter> filtersToUse = this.filters.stream()
.peek(filter -> {
if (filter instanceof ForwardedHeaderTransformer && this.forwardedHeaderTransformer == null) {
this.forwardedHeaderTransformer = (ForwardedHeaderTransformer) filter;
}
})
.filter(filter -> !(filter instanceof ForwardedHeaderTransformer))
.collect(Collectors.toList());
this.filters.clear();
this.filters.addAll(filtersToUse);
}
Add the given exception handler(s).
Params: - handlers – the exception handler(s)
/**
* 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.
Params: - consumer – the consumer to use
/**
* Manipulate the "live" list of currently configured exception handlers.
* @param consumer the consumer to use
*/
public WebHttpHandlerBuilder exceptionHandlers(Consumer<List<WebExceptionHandler>> consumer) {
consumer.accept(this.exceptionHandlers);
return this;
}
Configure the WebSessionManager
to set on the WebServerExchange
. By default DefaultWebSessionManager
is used.
Params: - manager – the session manager
See Also:
/**
* 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;
}
Whether a WebSessionManager
is configured or not, either detected from an ApplicationContext
or explicitly configured via sessionManager
. Since: 5.0.9
/**
* Whether a {@code WebSessionManager} is configured or not, either detected from an
* {@code ApplicationContext} or explicitly configured via {@link #sessionManager}.
* @since 5.0.9
*/
public boolean hasSessionManager() {
return (this.sessionManager != null);
}
Configure the ServerCodecConfigurer
to set on the WebServerExchange
. Params: - codecConfigurer – the codec configurer
/**
* Configure the {@link ServerCodecConfigurer} to set on the {@code WebServerExchange}.
* @param codecConfigurer the codec configurer
*/
public WebHttpHandlerBuilder codecConfigurer(ServerCodecConfigurer codecConfigurer) {
this.codecConfigurer = codecConfigurer;
return this;
}
Whether a ServerCodecConfigurer
is configured or not, either detected from an ApplicationContext
or explicitly configured via codecConfigurer
. Since: 5.0.9
/**
* Whether a {@code ServerCodecConfigurer} is configured or not, either detected from an
* {@code ApplicationContext} or explicitly configured via {@link #codecConfigurer}.
* @since 5.0.9
*/
public boolean hasCodecConfigurer() {
return (this.codecConfigurer != null);
}
Configure the LocaleContextResolver
to set on the WebServerExchange
. Params: - localeContextResolver – the locale context resolver
/**
* 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;
}
Whether a LocaleContextResolver
is configured or not, either detected from an ApplicationContext
or explicitly configured via localeContextResolver
. Since: 5.0.9
/**
* Whether a {@code LocaleContextResolver} is configured or not, either detected from an
* {@code ApplicationContext} or explicitly configured via {@link #localeContextResolver}.
* @since 5.0.9
*/
public boolean hasLocaleContextResolver() {
return (this.localeContextResolver != null);
}
Configure the ForwardedHeaderTransformer
for extracting and/or removing forwarded headers. Params: - transformer – the transformer
Since: 5.1
/**
* Configure the {@link ForwardedHeaderTransformer} for extracting and/or
* removing forwarded headers.
* @param transformer the transformer
* @since 5.1
*/
public WebHttpHandlerBuilder forwardedHeaderTransformer(ForwardedHeaderTransformer transformer) {
this.forwardedHeaderTransformer = transformer;
return this;
}
Whether a ForwardedHeaderTransformer
is configured or not, either detected from an ApplicationContext
or explicitly configured via forwardedHeaderTransformer(ForwardedHeaderTransformer)
. Since: 5.1
/**
* Whether a {@code ForwardedHeaderTransformer} is configured or not, either
* detected from an {@code ApplicationContext} or explicitly configured via
* {@link #forwardedHeaderTransformer(ForwardedHeaderTransformer)}.
* @since 5.1
*/
public boolean hasForwardedHeaderTransformer() {
return (this.forwardedHeaderTransformer != null);
}
Build the HttpHandler
. /**
* Build the {@link HttpHandler}.
*/
public HttpHandler build() {
WebHandler 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.forwardedHeaderTransformer != null) {
adapted.setForwardedHeaderTransformer(this.forwardedHeaderTransformer);
}
if (this.applicationContext != null) {
adapted.setApplicationContext(this.applicationContext);
}
adapted.afterPropertiesSet();
return adapted;
}
Clone this WebHttpHandlerBuilder
. Returns: the cloned builder instance
/**
* Clone this {@link WebHttpHandlerBuilder}.
* @return the cloned builder instance
*/
@Override
public WebHttpHandlerBuilder clone() {
return new WebHttpHandlerBuilder(this);
}
}