Polish WebHttpHandlerBuilder
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,8 +16,6 @@
|
||||
package org.springframework.web.server.adapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
@@ -65,7 +63,7 @@ public class WebHttpHandlerBuilder {
|
||||
public static final String WEB_SESSION_MANAGER_BEAN_NAME = "webSessionManager";
|
||||
|
||||
|
||||
private final WebHandler targetHandler;
|
||||
private final WebHandler webHandler;
|
||||
|
||||
private final List<WebFilter> filters = new ArrayList<>();
|
||||
|
||||
@@ -76,16 +74,15 @@ public class WebHttpHandlerBuilder {
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
* See factory method {@link #webHandler(WebHandler)}.
|
||||
*/
|
||||
private WebHttpHandlerBuilder(WebHandler targetHandler) {
|
||||
Assert.notNull(targetHandler, "WebHandler must not be null");
|
||||
this.targetHandler = targetHandler;
|
||||
private WebHttpHandlerBuilder(WebHandler webHandler) {
|
||||
Assert.notNull(webHandler, "WebHandler must not be null");
|
||||
this.webHandler = webHandler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory method to create a new builder instance.
|
||||
* Static factory method to create a new builder instance.
|
||||
* @param webHandler the target handler for the request
|
||||
* @return the prepared builder
|
||||
*/
|
||||
@@ -94,8 +91,8 @@ public class WebHttpHandlerBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new builder instance by detecting beans in an
|
||||
* {@link ApplicationContext}. The following are detected:
|
||||
* 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}.
|
||||
@@ -111,24 +108,15 @@ public class WebHttpHandlerBuilder {
|
||||
*/
|
||||
public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
|
||||
|
||||
// Target WebHandler
|
||||
|
||||
WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder(
|
||||
context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class));
|
||||
|
||||
// WebFilter...
|
||||
// Autowire lists for @Bean + @Order
|
||||
|
||||
AutowiredFiltersContainer filtersContainer = new AutowiredFiltersContainer();
|
||||
context.getAutowireCapableBeanFactory().autowireBean(filtersContainer);
|
||||
builder.filters(filtersContainer.getFilters());
|
||||
|
||||
// WebExceptionHandler...
|
||||
|
||||
AutowiredExceptionHandlersContainer handlersContainer = new AutowiredExceptionHandlersContainer();
|
||||
context.getAutowireCapableBeanFactory().autowireBean(handlersContainer);
|
||||
builder.exceptionHandlers(handlersContainer.getExceptionHandlers());
|
||||
|
||||
// WebSessionManager
|
||||
SortedBeanContainer container = new SortedBeanContainer();
|
||||
context.getAutowireCapableBeanFactory().autowireBean(container);
|
||||
builder.filters(container.getFilters());
|
||||
builder.exceptionHandlers(container.getExceptionHandlers());
|
||||
|
||||
try {
|
||||
builder.sessionManager(
|
||||
@@ -146,15 +134,7 @@ public class WebHttpHandlerBuilder {
|
||||
* Add the given filters to use for processing requests.
|
||||
* @param filters the filters to add
|
||||
*/
|
||||
public WebHttpHandlerBuilder filters(WebFilter... filters) {
|
||||
return filters(Arrays.asList(filters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given filters to use for processing requests.
|
||||
* @param filters the filters to add
|
||||
*/
|
||||
public WebHttpHandlerBuilder filters(Collection<? extends WebFilter> filters) {
|
||||
public WebHttpHandlerBuilder filters(List<? extends WebFilter> filters) {
|
||||
if (!ObjectUtils.isEmpty(filters)) {
|
||||
this.filters.addAll(filters);
|
||||
}
|
||||
@@ -163,19 +143,11 @@ public class WebHttpHandlerBuilder {
|
||||
|
||||
/**
|
||||
* Add the given exception handler to apply at the end of request processing.
|
||||
* @param exceptionHandlers the exception handlers
|
||||
* @param handlers the exception handlers
|
||||
*/
|
||||
public WebHttpHandlerBuilder exceptionHandlers(WebExceptionHandler... exceptionHandlers) {
|
||||
return exceptionHandlers(Arrays.asList(exceptionHandlers));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given exception handler to apply at the end of request processing.
|
||||
* @param exceptionHandlers the exception handlers
|
||||
*/
|
||||
public WebHttpHandlerBuilder exceptionHandlers(List<WebExceptionHandler> exceptionHandlers) {
|
||||
if (!ObjectUtils.isEmpty(exceptionHandlers)) {
|
||||
this.exceptionHandlers.addAll(exceptionHandlers);
|
||||
public WebHttpHandlerBuilder exceptionHandlers(List<WebExceptionHandler> handlers) {
|
||||
if (!ObjectUtils.isEmpty(handlers)) {
|
||||
this.exceptionHandlers.addAll(handlers);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -184,38 +156,41 @@ public class WebHttpHandlerBuilder {
|
||||
* Configure the {@link WebSessionManager} to set on the
|
||||
* {@link ServerWebExchange WebServerExchange}.
|
||||
* <p>By default {@link DefaultWebSessionManager} is used.
|
||||
* @param sessionManager the session manager
|
||||
* @param manager the session manager
|
||||
* @see HttpWebHandlerAdapter#setSessionManager(WebSessionManager)
|
||||
*/
|
||||
public WebHttpHandlerBuilder sessionManager(WebSessionManager sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
public WebHttpHandlerBuilder sessionManager(WebSessionManager manager) {
|
||||
this.sessionManager = manager;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build the {@link HttpHandler}.
|
||||
*/
|
||||
public HttpHandler build() {
|
||||
WebHandler webHandler = this.targetHandler;
|
||||
if (!this.filters.isEmpty()) {
|
||||
WebFilter[] array = new WebFilter[this.filters.size()];
|
||||
webHandler = new FilteringWebHandler(webHandler, this.filters.toArray(array));
|
||||
}
|
||||
WebExceptionHandler[] array = new WebExceptionHandler[this.exceptionHandlers.size()];
|
||||
webHandler = new ExceptionHandlingWebHandler(webHandler, this.exceptionHandlers.toArray(array));
|
||||
// TODO: protected method for further decoration
|
||||
HttpWebHandlerAdapter httpHandler = new HttpWebHandlerAdapter(webHandler);
|
||||
|
||||
WebHandler decorated;
|
||||
|
||||
decorated = new FilteringWebHandler(this.webHandler, this.filters);
|
||||
decorated = new ExceptionHandlingWebHandler(decorated, this.exceptionHandlers);
|
||||
|
||||
HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
|
||||
if (this.sessionManager != null) {
|
||||
httpHandler.setSessionManager(this.sessionManager);
|
||||
adapted.setSessionManager(this.sessionManager);
|
||||
}
|
||||
return httpHandler;
|
||||
|
||||
return adapted;
|
||||
}
|
||||
|
||||
|
||||
private static class AutowiredFiltersContainer {
|
||||
private static class SortedBeanContainer {
|
||||
|
||||
private List<WebFilter> filters;
|
||||
|
||||
private List<WebExceptionHandler> exceptionHandlers;
|
||||
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setFilters(List<WebFilter> filters) {
|
||||
this.filters = filters;
|
||||
@@ -224,11 +199,6 @@ public class WebHttpHandlerBuilder {
|
||||
public List<WebFilter> getFilters() {
|
||||
return this.filters;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AutowiredExceptionHandlersContainer {
|
||||
|
||||
private List<WebExceptionHandler> exceptionHandlers;
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setExceptionHandlers(List<WebExceptionHandler> exceptionHandlers) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.web.server.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -33,8 +34,8 @@ import org.springframework.web.server.WebExceptionHandler;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
|
||||
/**
|
||||
* WebHandler that can invoke a target {@link WebHandler} and then apply
|
||||
* exception handling with one or more {@link WebExceptionHandler} instances.
|
||||
* WebHandler decorator that invokes one or more {@link WebExceptionHandler}s
|
||||
* after the delegate {@link WebHandler}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
@@ -42,41 +43,39 @@ import org.springframework.web.server.WebHandler;
|
||||
public class ExceptionHandlingWebHandler extends WebHandlerDecorator {
|
||||
|
||||
/**
|
||||
* Log category to use on network IO exceptions after a client has gone away.
|
||||
* <p>Servlet containers do not expose notifications when a client disconnects;
|
||||
* see <a href="https://java.net/jira/browse/SERVLET_SPEC-44">SERVLET_SPEC-44</a>.
|
||||
* Therefore network IO failures may occur simply because a client has gone away,
|
||||
* and that can fill the logs with unnecessary stack traces.
|
||||
* <p>We make a best effort to identify such network failures, on a per-server
|
||||
* basis and log them under a separate log category. A simple one-line message
|
||||
* is logged at DEBUG level instead while a full stack trace is shown at TRACE.
|
||||
* Dedicated log category for disconnected client exceptions.
|
||||
* <p>Servlet containers do not expose a notification when a client disconnects,
|
||||
* e.g. <a href="https://java.net/jira/browse/SERVLET_SPEC-44">SERVLET_SPEC-44</a>.
|
||||
* <p>To avoid filling logs with unnecessary stack traces, we make an
|
||||
* effort to identify such network failures on a per-server basis, and then
|
||||
* log under a separate log category a simple one-line message at DEBUG level
|
||||
* or a full stack trace only at TRACE level.
|
||||
*/
|
||||
private static final String DISCONNECTED_CLIENT_LOG_CATEGORY =
|
||||
ExceptionHandlingWebHandler.class.getName() + ".DisconnectedClient";
|
||||
|
||||
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS;
|
||||
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS =
|
||||
new HashSet<>(Arrays.asList("ClientAbortException", "EOFException", "EofException"));
|
||||
|
||||
static {
|
||||
Set<String> set = new HashSet<>(3);
|
||||
set.add("ClientAbortException"); // Tomcat
|
||||
set.add("EOFException"); // Tomcat
|
||||
set.add("EofException"); // Jetty
|
||||
// java.io.IOException("Broken pipe") on WildFly (already covered)
|
||||
DISCONNECTED_CLIENT_EXCEPTIONS = Collections.unmodifiableSet(set);
|
||||
}
|
||||
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ExceptionHandlingWebHandler.class);
|
||||
|
||||
private static final Log disconnectedClientLogger = LogFactory.getLog(DISCONNECTED_CLIENT_LOG_CATEGORY);
|
||||
|
||||
|
||||
private final List<WebExceptionHandler> exceptionHandlers;
|
||||
|
||||
|
||||
public ExceptionHandlingWebHandler(WebHandler delegate, WebExceptionHandler... exceptionHandlers) {
|
||||
public ExceptionHandlingWebHandler(WebHandler delegate, List<WebExceptionHandler> handlers) {
|
||||
super(delegate);
|
||||
this.exceptionHandlers = (exceptionHandlers != null ?
|
||||
Collections.unmodifiableList(Arrays.asList(exceptionHandlers)): Collections.emptyList());
|
||||
this.exceptionHandlers = initHandlers(handlers);
|
||||
}
|
||||
|
||||
private List<WebExceptionHandler> initHandlers(List<WebExceptionHandler> handlers) {
|
||||
List<WebExceptionHandler> result = new ArrayList<>(handlers);
|
||||
result.add(new UnresolvedExceptionHandler());
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,44 +89,53 @@ public class ExceptionHandlingWebHandler extends WebHandlerDecorator {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange) {
|
||||
Mono<Void> mono;
|
||||
|
||||
Mono<Void> completion;
|
||||
try {
|
||||
mono = getDelegate().handle(exchange);
|
||||
completion = super.handle(exchange);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
mono = Mono.error(ex);
|
||||
completion = Mono.error(ex);
|
||||
}
|
||||
for (WebExceptionHandler exceptionHandler : this.exceptionHandlers) {
|
||||
mono = mono.otherwise(ex -> exceptionHandler.handle(exchange, ex));
|
||||
|
||||
for (WebExceptionHandler handler : this.exceptionHandlers) {
|
||||
completion = completion.otherwise(ex -> handler.handle(exchange, ex));
|
||||
}
|
||||
return mono.otherwise(ex -> handleUnresolvedException(exchange, ex));
|
||||
|
||||
return completion;
|
||||
}
|
||||
|
||||
private Mono<? extends Void> handleUnresolvedException(ServerWebExchange exchange, Throwable ex) {
|
||||
logException(ex);
|
||||
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
private void logException(Throwable ex) {
|
||||
private static class UnresolvedExceptionHandler implements WebExceptionHandler {
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
logException(ex);
|
||||
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
NestedCheckedException nestedException = new NestedCheckedException("", ex) {};
|
||||
private void logException(Throwable ex) {
|
||||
NestedCheckedException nestedEx = new NestedCheckedException("", ex) {};
|
||||
if ("Broken pipe".equalsIgnoreCase(nestedEx.getMostSpecificCause().getMessage()) ||
|
||||
DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName())) {
|
||||
|
||||
if ("Broken pipe".equalsIgnoreCase(nestedException.getMostSpecificCause().getMessage()) ||
|
||||
DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName())) {
|
||||
|
||||
if (disconnectedClientLogger.isTraceEnabled()) {
|
||||
disconnectedClientLogger.trace("Looks like the client has gone away", ex);
|
||||
if (disconnectedClientLogger.isTraceEnabled()) {
|
||||
disconnectedClientLogger.trace("Looks like the client has gone away", ex);
|
||||
}
|
||||
else if (disconnectedClientLogger.isDebugEnabled()) {
|
||||
disconnectedClientLogger.debug(
|
||||
"The client has gone away: " + nestedEx.getMessage() +
|
||||
" (For a full stack trace, set the log category" +
|
||||
"'" + DISCONNECTED_CLIENT_LOG_CATEGORY + "' to TRACE)");
|
||||
}
|
||||
}
|
||||
else if (disconnectedClientLogger.isDebugEnabled()) {
|
||||
disconnectedClientLogger.debug("Looks like the client has gone away: " +
|
||||
nestedException.getMessage() + " (For full stack trace, set the '" +
|
||||
DISCONNECTED_CLIENT_LOG_CATEGORY + "' log category to TRACE level)");
|
||||
else {
|
||||
logger.error("Could not complete request", ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.error("Could not complete request", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.web.server.handler;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -28,8 +27,8 @@ import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
|
||||
/**
|
||||
* WebHandler that delegates to a chain of {@link WebFilter} instances and then
|
||||
* to the target {@link WebHandler}.
|
||||
* WebHandler decorator that invokes a chain of {@link WebFilter}s before the
|
||||
* delegate {@link WebHandler}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
@@ -39,25 +38,29 @@ public class FilteringWebHandler extends WebHandlerDecorator {
|
||||
private final List<WebFilter> filters;
|
||||
|
||||
|
||||
public FilteringWebHandler(WebHandler targetHandler, WebFilter... filters) {
|
||||
super(targetHandler);
|
||||
this.filters = initList(filters);
|
||||
}
|
||||
|
||||
private static List<WebFilter> initList(WebFilter[] list) {
|
||||
return (list != null ? Collections.unmodifiableList(Arrays.asList(list)) : Collections.emptyList());
|
||||
/**
|
||||
* Constructor.
|
||||
* @param filters the chain of filters
|
||||
*/
|
||||
public FilteringWebHandler(WebHandler webHandler, List<WebFilter> filters) {
|
||||
super(webHandler);
|
||||
this.filters = Collections.unmodifiableList(filters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a read-only list of the configured filters.
|
||||
* Return read-only list of the configured filters.
|
||||
*/
|
||||
public List<WebFilter> getFilters() {
|
||||
return this.filters;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange) {
|
||||
if (this.filters.isEmpty()) {
|
||||
return super.handle(exchange);
|
||||
}
|
||||
return new DefaultWebFilterChain().filter(exchange);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user