001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.server.adapter; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.List; 022import java.util.function.Consumer; 023import java.util.stream.Collectors; 024 025import org.springframework.beans.factory.NoSuchBeanDefinitionException; 026import org.springframework.context.ApplicationContext; 027import org.springframework.core.annotation.AnnotationAwareOrderComparator; 028import org.springframework.http.codec.ServerCodecConfigurer; 029import org.springframework.http.server.reactive.HttpHandler; 030import org.springframework.lang.Nullable; 031import org.springframework.util.Assert; 032import org.springframework.util.ObjectUtils; 033import org.springframework.web.server.ServerWebExchange; 034import org.springframework.web.server.WebExceptionHandler; 035import org.springframework.web.server.WebFilter; 036import org.springframework.web.server.WebHandler; 037import org.springframework.web.server.handler.ExceptionHandlingWebHandler; 038import org.springframework.web.server.handler.FilteringWebHandler; 039import org.springframework.web.server.i18n.LocaleContextResolver; 040import org.springframework.web.server.session.DefaultWebSessionManager; 041import org.springframework.web.server.session.WebSessionManager; 042 043/** 044 * This builder has two purposes: 045 * 046 * <p>One is to assemble a processing chain that consists of a target {@link WebHandler}, 047 * then decorated with a set of {@link WebFilter WebFilters}, then further decorated with 048 * a set of {@link WebExceptionHandler WebExceptionHandlers}. 049 * 050 * <p>The second purpose is to adapt the resulting processing chain to an {@link HttpHandler}: 051 * the lowest-level reactive HTTP handling abstraction which can then be used with any of the 052 * supported runtimes. The adaptation is done with the help of {@link HttpWebHandlerAdapter}. 053 * 054 * <p>The processing chain can be assembled manually via builder methods, or detected from 055 * a Spring {@link ApplicationContext} via {@link #applicationContext}, or a mix of both. 056 * 057 * @author Rossen Stoyanchev 058 * @author Sebastien Deleuze 059 * @since 5.0 060 * @see HttpWebHandlerAdapter 061 */ 062public final class WebHttpHandlerBuilder { 063 064 /** Well-known name for the target WebHandler in the bean factory. */ 065 public static final String WEB_HANDLER_BEAN_NAME = "webHandler"; 066 067 /** Well-known name for the WebSessionManager in the bean factory. */ 068 public static final String WEB_SESSION_MANAGER_BEAN_NAME = "webSessionManager"; 069 070 /** Well-known name for the ServerCodecConfigurer in the bean factory. */ 071 public static final String SERVER_CODEC_CONFIGURER_BEAN_NAME = "serverCodecConfigurer"; 072 073 /** Well-known name for the LocaleContextResolver in the bean factory. */ 074 public static final String LOCALE_CONTEXT_RESOLVER_BEAN_NAME = "localeContextResolver"; 075 076 /** Well-known name for the ForwardedHeaderTransformer in the bean factory. */ 077 public static final String FORWARDED_HEADER_TRANSFORMER_BEAN_NAME = "forwardedHeaderTransformer"; 078 079 080 private final WebHandler webHandler; 081 082 @Nullable 083 private final ApplicationContext applicationContext; 084 085 private final List<WebFilter> filters = new ArrayList<>(); 086 087 private final List<WebExceptionHandler> exceptionHandlers = new ArrayList<>(); 088 089 @Nullable 090 private WebSessionManager sessionManager; 091 092 @Nullable 093 private ServerCodecConfigurer codecConfigurer; 094 095 @Nullable 096 private LocaleContextResolver localeContextResolver; 097 098 @Nullable 099 private ForwardedHeaderTransformer forwardedHeaderTransformer; 100 101 102 /** 103 * Private constructor to use when initialized from an ApplicationContext. 104 */ 105 private WebHttpHandlerBuilder(WebHandler webHandler, @Nullable ApplicationContext applicationContext) { 106 Assert.notNull(webHandler, "WebHandler must not be null"); 107 this.webHandler = webHandler; 108 this.applicationContext = applicationContext; 109 } 110 111 /** 112 * Copy constructor. 113 */ 114 private WebHttpHandlerBuilder(WebHttpHandlerBuilder other) { 115 this.webHandler = other.webHandler; 116 this.applicationContext = other.applicationContext; 117 this.filters.addAll(other.filters); 118 this.exceptionHandlers.addAll(other.exceptionHandlers); 119 this.sessionManager = other.sessionManager; 120 this.codecConfigurer = other.codecConfigurer; 121 this.localeContextResolver = other.localeContextResolver; 122 this.forwardedHeaderTransformer = other.forwardedHeaderTransformer; 123 } 124 125 126 /** 127 * Static factory method to create a new builder instance. 128 * @param webHandler the target handler for the request 129 * @return the prepared builder 130 */ 131 public static WebHttpHandlerBuilder webHandler(WebHandler webHandler) { 132 return new WebHttpHandlerBuilder(webHandler, null); 133 } 134 135 /** 136 * Static factory method to create a new builder instance by detecting beans 137 * in an {@link ApplicationContext}. The following are detected: 138 * <ul> 139 * <li>{@link WebHandler} [1] -- looked up by the name 140 * {@link #WEB_HANDLER_BEAN_NAME}. 141 * <li>{@link WebFilter} [0..N] -- detected by type and ordered, 142 * see {@link AnnotationAwareOrderComparator}. 143 * <li>{@link WebExceptionHandler} [0..N] -- detected by type and 144 * ordered. 145 * <li>{@link WebSessionManager} [0..1] -- looked up by the name 146 * {@link #WEB_SESSION_MANAGER_BEAN_NAME}. 147 * <li>{@link ServerCodecConfigurer} [0..1] -- looked up by the name 148 * {@link #SERVER_CODEC_CONFIGURER_BEAN_NAME}. 149 * <li>{@link LocaleContextResolver} [0..1] -- looked up by the name 150 * {@link #LOCALE_CONTEXT_RESOLVER_BEAN_NAME}. 151 * </ul> 152 * @param context the application context to use for the lookup 153 * @return the prepared builder 154 */ 155 public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) { 156 WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder( 157 context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class), context); 158 159 List<WebFilter> webFilters = context 160 .getBeanProvider(WebFilter.class) 161 .orderedStream() 162 .collect(Collectors.toList()); 163 builder.filters(filters -> filters.addAll(webFilters)); 164 List<WebExceptionHandler> exceptionHandlers = context 165 .getBeanProvider(WebExceptionHandler.class) 166 .orderedStream() 167 .collect(Collectors.toList()); 168 builder.exceptionHandlers(handlers -> handlers.addAll(exceptionHandlers)); 169 170 try { 171 builder.sessionManager( 172 context.getBean(WEB_SESSION_MANAGER_BEAN_NAME, WebSessionManager.class)); 173 } 174 catch (NoSuchBeanDefinitionException ex) { 175 // Fall back on default 176 } 177 178 try { 179 builder.codecConfigurer( 180 context.getBean(SERVER_CODEC_CONFIGURER_BEAN_NAME, ServerCodecConfigurer.class)); 181 } 182 catch (NoSuchBeanDefinitionException ex) { 183 // Fall back on default 184 } 185 186 try { 187 builder.localeContextResolver( 188 context.getBean(LOCALE_CONTEXT_RESOLVER_BEAN_NAME, LocaleContextResolver.class)); 189 } 190 catch (NoSuchBeanDefinitionException ex) { 191 // Fall back on default 192 } 193 194 try { 195 builder.forwardedHeaderTransformer( 196 context.getBean(FORWARDED_HEADER_TRANSFORMER_BEAN_NAME, ForwardedHeaderTransformer.class)); 197 } 198 catch (NoSuchBeanDefinitionException ex) { 199 // Fall back on default 200 } 201 202 return builder; 203 } 204 205 206 /** 207 * Add the given filter(s). 208 * @param filters the filter(s) to add that's 209 */ 210 public WebHttpHandlerBuilder filter(WebFilter... filters) { 211 if (!ObjectUtils.isEmpty(filters)) { 212 this.filters.addAll(Arrays.asList(filters)); 213 updateFilters(); 214 } 215 return this; 216 } 217 218 /** 219 * Manipulate the "live" list of currently configured filters. 220 * @param consumer the consumer to use 221 */ 222 public WebHttpHandlerBuilder filters(Consumer<List<WebFilter>> consumer) { 223 consumer.accept(this.filters); 224 updateFilters(); 225 return this; 226 } 227 228 private void updateFilters() { 229 if (this.filters.isEmpty()) { 230 return; 231 } 232 233 List<WebFilter> filtersToUse = this.filters.stream() 234 .peek(filter -> { 235 if (filter instanceof ForwardedHeaderTransformer && this.forwardedHeaderTransformer == null) { 236 this.forwardedHeaderTransformer = (ForwardedHeaderTransformer) filter; 237 } 238 }) 239 .filter(filter -> !(filter instanceof ForwardedHeaderTransformer)) 240 .collect(Collectors.toList()); 241 242 this.filters.clear(); 243 this.filters.addAll(filtersToUse); 244 } 245 246 /** 247 * Add the given exception handler(s). 248 * @param handlers the exception handler(s) 249 */ 250 public WebHttpHandlerBuilder exceptionHandler(WebExceptionHandler... handlers) { 251 if (!ObjectUtils.isEmpty(handlers)) { 252 this.exceptionHandlers.addAll(Arrays.asList(handlers)); 253 } 254 return this; 255 } 256 257 /** 258 * Manipulate the "live" list of currently configured exception handlers. 259 * @param consumer the consumer to use 260 */ 261 public WebHttpHandlerBuilder exceptionHandlers(Consumer<List<WebExceptionHandler>> consumer) { 262 consumer.accept(this.exceptionHandlers); 263 return this; 264 } 265 266 /** 267 * Configure the {@link WebSessionManager} to set on the 268 * {@link ServerWebExchange WebServerExchange}. 269 * <p>By default {@link DefaultWebSessionManager} is used. 270 * @param manager the session manager 271 * @see HttpWebHandlerAdapter#setSessionManager(WebSessionManager) 272 */ 273 public WebHttpHandlerBuilder sessionManager(WebSessionManager manager) { 274 this.sessionManager = manager; 275 return this; 276 } 277 278 /** 279 * Whether a {@code WebSessionManager} is configured or not, either detected from an 280 * {@code ApplicationContext} or explicitly configured via {@link #sessionManager}. 281 * @since 5.0.9 282 */ 283 public boolean hasSessionManager() { 284 return (this.sessionManager != null); 285 } 286 287 /** 288 * Configure the {@link ServerCodecConfigurer} to set on the {@code WebServerExchange}. 289 * @param codecConfigurer the codec configurer 290 */ 291 public WebHttpHandlerBuilder codecConfigurer(ServerCodecConfigurer codecConfigurer) { 292 this.codecConfigurer = codecConfigurer; 293 return this; 294 } 295 296 297 /** 298 * Whether a {@code ServerCodecConfigurer} is configured or not, either detected from an 299 * {@code ApplicationContext} or explicitly configured via {@link #codecConfigurer}. 300 * @since 5.0.9 301 */ 302 public boolean hasCodecConfigurer() { 303 return (this.codecConfigurer != null); 304 } 305 306 /** 307 * Configure the {@link LocaleContextResolver} to set on the 308 * {@link ServerWebExchange WebServerExchange}. 309 * @param localeContextResolver the locale context resolver 310 */ 311 public WebHttpHandlerBuilder localeContextResolver(LocaleContextResolver localeContextResolver) { 312 this.localeContextResolver = localeContextResolver; 313 return this; 314 } 315 316 /** 317 * Whether a {@code LocaleContextResolver} is configured or not, either detected from an 318 * {@code ApplicationContext} or explicitly configured via {@link #localeContextResolver}. 319 * @since 5.0.9 320 */ 321 public boolean hasLocaleContextResolver() { 322 return (this.localeContextResolver != null); 323 } 324 325 /** 326 * Configure the {@link ForwardedHeaderTransformer} for extracting and/or 327 * removing forwarded headers. 328 * @param transformer the transformer 329 * @since 5.1 330 */ 331 public WebHttpHandlerBuilder forwardedHeaderTransformer(ForwardedHeaderTransformer transformer) { 332 this.forwardedHeaderTransformer = transformer; 333 return this; 334 } 335 336 /** 337 * Whether a {@code ForwardedHeaderTransformer} is configured or not, either 338 * detected from an {@code ApplicationContext} or explicitly configured via 339 * {@link #forwardedHeaderTransformer(ForwardedHeaderTransformer)}. 340 * @since 5.1 341 */ 342 public boolean hasForwardedHeaderTransformer() { 343 return (this.forwardedHeaderTransformer != null); 344 } 345 346 347 /** 348 * Build the {@link HttpHandler}. 349 */ 350 public HttpHandler build() { 351 WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters); 352 decorated = new ExceptionHandlingWebHandler(decorated, this.exceptionHandlers); 353 354 HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated); 355 if (this.sessionManager != null) { 356 adapted.setSessionManager(this.sessionManager); 357 } 358 if (this.codecConfigurer != null) { 359 adapted.setCodecConfigurer(this.codecConfigurer); 360 } 361 if (this.localeContextResolver != null) { 362 adapted.setLocaleContextResolver(this.localeContextResolver); 363 } 364 if (this.forwardedHeaderTransformer != null) { 365 adapted.setForwardedHeaderTransformer(this.forwardedHeaderTransformer); 366 } 367 if (this.applicationContext != null) { 368 adapted.setApplicationContext(this.applicationContext); 369 } 370 adapted.afterPropertiesSet(); 371 372 return adapted; 373 } 374 375 /** 376 * Clone this {@link WebHttpHandlerBuilder}. 377 * @return the cloned builder instance 378 */ 379 @Override 380 public WebHttpHandlerBuilder clone() { 381 return new WebHttpHandlerBuilder(this); 382 } 383 384}