WebHttpHandlerBuilder.java 13.5 KB
Newer Older
1
/*
2
 * Copyright 2002-2018 the original author or authors.
3 4 5 6 7 8 9 10 11 12 13 14 15
 *
 * 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.
 */
16

17 18 19
package org.springframework.web.server.adapter;

import java.util.ArrayList;
20
import java.util.Arrays;
21
import java.util.Collections;
22
import java.util.List;
23
import java.util.function.Consumer;
24
import java.util.stream.Collectors;
25

26
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
27
import org.springframework.beans.factory.annotation.Autowired;
28 29
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
30
import org.springframework.http.codec.ServerCodecConfigurer;
31
import org.springframework.http.server.reactive.HttpHandler;
32
import org.springframework.lang.Nullable;
33 34 35 36 37 38 39 40
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;
41
import org.springframework.web.server.i18n.LocaleContextResolver;
42
import org.springframework.web.server.session.DefaultWebSessionManager;
43 44 45
import org.springframework.web.server.session.WebSessionManager;

/**
J
Juergen Hoeller 已提交
46
 * This builder has two purposes:
47
 *
J
Juergen Hoeller 已提交
48 49 50
 * <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}.
51
 *
J
Juergen Hoeller 已提交
52 53 54
 * <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}.
55
 *
J
Juergen Hoeller 已提交
56 57
 * <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.
58 59
 *
 * @author Rossen Stoyanchev
60
 * @author Sebastien Deleuze
61
 * @since 5.0
62
 * @see HttpWebHandlerAdapter
63
 */
P
Phillip Webb 已提交
64
public final class WebHttpHandlerBuilder {
65

66 67 68 69 70 71
	/** 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";

72 73 74
	/** Well-known name for the ServerCodecConfigurer in the bean factory. */
	public static final String SERVER_CODEC_CONFIGURER_BEAN_NAME = "serverCodecConfigurer";

75 76 77
	/** Well-known name for the LocaleContextResolver in the bean factory. */
	public static final String LOCALE_CONTEXT_RESOLVER_BEAN_NAME = "localeContextResolver";

78 79 80
	/** Well-known name for the ForwardedHeaderTransformer in the bean factory. */
	public static final String FORWARDED_HEADER_TRANSFORMER_BEAN_NAME = "forwardedHeaderTransformer";

81

R
Rossen Stoyanchev 已提交
82
	private final WebHandler webHandler;
83

84 85 86
	@Nullable
	private final ApplicationContext applicationContext;

87 88 89 90
	private final List<WebFilter> filters = new ArrayList<>();

	private final List<WebExceptionHandler> exceptionHandlers = new ArrayList<>();

91
	@Nullable
92 93
	private WebSessionManager sessionManager;

94
	@Nullable
95 96
	private ServerCodecConfigurer codecConfigurer;

97
	@Nullable
98 99
	private LocaleContextResolver localeContextResolver;

100 101 102
	@Nullable
	private ForwardedHeaderTransformer forwardedHeaderTransformer;

103

104 105 106
	/**
	 * Private constructor to use when initialized from an ApplicationContext.
	 */
107
	private WebHttpHandlerBuilder(WebHandler webHandler, @Nullable ApplicationContext applicationContext) {
108 109 110 111 112
		Assert.notNull(webHandler, "WebHandler must not be null");
		this.webHandler = webHandler;
		this.applicationContext = applicationContext;
	}

R
Rossen Stoyanchev 已提交
113 114 115 116 117
	/**
	 * Copy constructor.
	 */
	private WebHttpHandlerBuilder(WebHttpHandlerBuilder other) {
		this.webHandler = other.webHandler;
118
		this.applicationContext = other.applicationContext;
R
Rossen Stoyanchev 已提交
119 120 121 122 123
		this.filters.addAll(other.filters);
		this.exceptionHandlers.addAll(other.exceptionHandlers);
		this.sessionManager = other.sessionManager;
		this.codecConfigurer = other.codecConfigurer;
		this.localeContextResolver = other.localeContextResolver;
124
		this.forwardedHeaderTransformer = other.forwardedHeaderTransformer;
R
Rossen Stoyanchev 已提交
125 126
	}

127 128

	/**
R
Rossen Stoyanchev 已提交
129
	 * Static factory method to create a new builder instance.
130
	 * @param webHandler the target handler for the request
131
	 * @return the prepared builder
132
	 */
133
	public static WebHttpHandlerBuilder webHandler(WebHandler webHandler) {
134
		return new WebHttpHandlerBuilder(webHandler, null);
135 136
	}

137
	/**
R
Rossen Stoyanchev 已提交
138 139
	 * Static factory method to create a new builder instance by detecting beans
	 * in an {@link ApplicationContext}. The following are detected:
140
	 * <ul>
141 142 143 144 145 146 147 148 149 150 151 152
	 * <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}.
153 154 155 156 157 158
	 * </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(
159
				context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class), context);
160

R
Rossen Stoyanchev 已提交
161
		// Autowire lists for @Bean + @Order
162

R
Rossen Stoyanchev 已提交
163 164
		SortedBeanContainer container = new SortedBeanContainer();
		context.getAutowireCapableBeanFactory().autowireBean(container);
165 166
		builder.filters(filters -> filters.addAll(container.getFilters()));
		builder.exceptionHandlers(handlers -> handlers.addAll(container.getExceptionHandlers()));
167 168 169 170 171 172 173 174 175

		try {
			builder.sessionManager(
					context.getBean(WEB_SESSION_MANAGER_BEAN_NAME, WebSessionManager.class));
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Fall back on default
		}

176 177 178 179 180 181 182 183
		try {
			builder.codecConfigurer(
					context.getBean(SERVER_CODEC_CONFIGURER_BEAN_NAME, ServerCodecConfigurer.class));
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Fall back on default
		}

184 185 186 187 188 189 190 191
		try {
			builder.localeContextResolver(
					context.getBean(LOCALE_CONTEXT_RESOLVER_BEAN_NAME, LocaleContextResolver.class));
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Fall back on default
		}

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
		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
		}

208 209 210
		return builder;
	}

211

212 213
	/**
	 * Add the given filter(s).
214 215
	 * @param filters the filter(s) to add that's
	 */
216 217 218
	public WebHttpHandlerBuilder filter(WebFilter... filters) {
		if (!ObjectUtils.isEmpty(filters)) {
			this.filters.addAll(Arrays.asList(filters));
219
			updateFilters();
220 221 222 223
		}
		return this;
	}

224
	/**
225 226
	 * Manipulate the "live" list of currently configured filters.
	 * @param consumer the consumer to use
227
	 */
228 229
	public WebHttpHandlerBuilder filters(Consumer<List<WebFilter>> consumer) {
		consumer.accept(this.filters);
230
		updateFilters();
231 232 233
		return this;
	}

234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
	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);
	}

253 254 255 256 257 258 259 260 261 262 263
	/**
	 * 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;
	}

264
	/**
265 266
	 * Manipulate the "live" list of currently configured exception handlers.
	 * @param consumer the consumer to use
267
	 */
268 269
	public WebHttpHandlerBuilder exceptionHandlers(Consumer<List<WebExceptionHandler>> consumer) {
		consumer.accept(this.exceptionHandlers);
270 271 272
		return this;
	}

273 274
	/**
	 * Configure the {@link WebSessionManager} to set on the
275 276
	 * {@link ServerWebExchange WebServerExchange}.
	 * <p>By default {@link DefaultWebSessionManager} is used.
R
Rossen Stoyanchev 已提交
277
	 * @param manager the session manager
278
	 * @see HttpWebHandlerAdapter#setSessionManager(WebSessionManager)
279
	 */
R
Rossen Stoyanchev 已提交
280 281
	public WebHttpHandlerBuilder sessionManager(WebSessionManager manager) {
		this.sessionManager = manager;
282 283 284
		return this;
	}

285
	/**
J
Juergen Hoeller 已提交
286 287
	 * Whether a {@code WebSessionManager} is configured or not, either detected from an
	 * {@code ApplicationContext} or explicitly configured via {@link #sessionManager}.
288 289 290
	 * @since 5.0.9
	 */
	public boolean hasSessionManager() {
J
Juergen Hoeller 已提交
291
		return (this.sessionManager != null);
292 293 294 295
	}

	/**
	 * Configure the {@link ServerCodecConfigurer} to set on the {@code WebServerExchange}.
296 297 298 299 300 301 302
	 * @param codecConfigurer the codec configurer
	 */
	public WebHttpHandlerBuilder codecConfigurer(ServerCodecConfigurer codecConfigurer) {
		this.codecConfigurer = codecConfigurer;
		return this;
	}

303 304

	/**
J
Juergen Hoeller 已提交
305 306
	 * Whether a {@code ServerCodecConfigurer} is configured or not, either detected from an
	 * {@code ApplicationContext} or explicitly configured via {@link #codecConfigurer}.
307 308 309
	 * @since 5.0.9
	 */
	public boolean hasCodecConfigurer() {
J
Juergen Hoeller 已提交
310
		return (this.codecConfigurer != null);
311 312
	}

313 314 315 316 317 318 319 320 321 322
	/**
	 * 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;
	}

323
	/**
J
Juergen Hoeller 已提交
324 325
	 * Whether a {@code LocaleContextResolver} is configured or not, either detected from an
	 * {@code ApplicationContext} or explicitly configured via {@link #localeContextResolver}.
326 327 328
	 * @since 5.0.9
	 */
	public boolean hasLocaleContextResolver() {
J
Juergen Hoeller 已提交
329
		return (this.localeContextResolver != null);
330 331
	}

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
	/**
	 * 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 {@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);
	}

R
Rossen Stoyanchev 已提交
353

354 355 356 357
	/**
	 * Build the {@link HttpHandler}.
	 */
	public HttpHandler build() {
358

359
		WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
R
Rossen Stoyanchev 已提交
360 361 362
		decorated = new ExceptionHandlingWebHandler(decorated,  this.exceptionHandlers);

		HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
363
		if (this.sessionManager != null) {
R
Rossen Stoyanchev 已提交
364
			adapted.setSessionManager(this.sessionManager);
365
		}
366 367 368
		if (this.codecConfigurer != null) {
			adapted.setCodecConfigurer(this.codecConfigurer);
		}
369 370 371
		if (this.localeContextResolver != null) {
			adapted.setLocaleContextResolver(this.localeContextResolver);
		}
372 373 374
		if (this.forwardedHeaderTransformer != null) {
			adapted.setForwardedHeaderTransformer(this.forwardedHeaderTransformer);
		}
375 376 377
		if (this.applicationContext != null) {
			adapted.setApplicationContext(this.applicationContext);
		}
378 379
		adapted.afterPropertiesSet();

R
Rossen Stoyanchev 已提交
380
		return adapted;
381 382
	}

R
Rossen Stoyanchev 已提交
383 384 385 386
	/**
	 * Clone this {@link WebHttpHandlerBuilder}.
	 * @return the cloned builder instance
	 */
R
Polish  
Rossen Stoyanchev 已提交
387 388
	@Override
	public WebHttpHandlerBuilder clone() {
R
Rossen Stoyanchev 已提交
389 390 391
		return new WebHttpHandlerBuilder(this);
	}

392

R
Rossen Stoyanchev 已提交
393
	private static class SortedBeanContainer {
394

395
		private List<WebFilter> filters = Collections.emptyList();
396

397
		private List<WebExceptionHandler> exceptionHandlers = Collections.emptyList();
R
Rossen Stoyanchev 已提交
398

399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
		@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;
		}
	}

418
}