/*
 * 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:
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; }
Returns:the cloned builder instance
/** * Clone this {@link WebHttpHandlerBuilder}. * @return the cloned builder instance */
@Override public WebHttpHandlerBuilder clone() { return new WebHttpHandlerBuilder(this); } }