Improve async request timeout handling

Rather than setting the status to 503 directly from the timeout
interceptor which no longer seems to work reliably with Servlet
containers like Jetty even performing an additional ERROR dispatch back
to the original URL, we know rather set the DeferredResult to an
AsyncTimeoutException, which results in a dispatch and standard
handling within Spring MVC. This should be a more reliable way of
dealing with timeouts.

Issue: SPR-14669
This commit is contained in:
Rossen Stoyanchev
2016-09-14 21:34:30 -04:00
parent 8ccfecc406
commit 765b47246a
8 changed files with 124 additions and 29 deletions

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2002-2016 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.context.request.async;
/**
* Exception to be thrown when an async request times out.
* Alternatively an applications can register a
* {@link DeferredResultProcessingInterceptor} or a
* {@link CallableProcessingInterceptor} to handle the timeout through
* the MVC Java config or the MVC XML namespace or directly through properties
* of the {@code RequestMappingHandlerAdapter}.
*
* <p>By default the exception will be handled as a 503 error.
*
* @author Rossen Stoyanchev
* @since 4.2.8
*/
@SuppressWarnings("serial")
public class AsyncRequestTimeoutException extends Exception {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@@ -24,7 +24,11 @@ import org.springframework.web.context.request.NativeWebRequest;
/**
* Sends a 503 (SERVICE_UNAVAILABLE) in case of a timeout if the response is not
* already committed. Registered at the end, after all other interceptors and
* already committed. As of 4.2.8 this is done indirectly by setting the result
* to an {@link AsyncRequestTimeoutException} which is then handled by
* Spring MVC's default exception handling as a 503 error.
*
* <p>Registered at the end, after all other interceptors and
* therefore invoked only if no other interceptor handles the timeout.
*
* <p>Note that according to RFC 7231, a 503 without a 'Retry-After' header is
@@ -39,11 +43,7 @@ public class TimeoutCallableProcessingInterceptor extends CallableProcessingInte
@Override
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
HttpServletResponse servletResponse = request.getNativeResponse(HttpServletResponse.class);
if (!servletResponse.isCommitted()) {
servletResponse.sendError(HttpStatus.SERVICE_UNAVAILABLE.value());
}
return CallableProcessingInterceptor.RESPONSE_HANDLED;
return new AsyncRequestTimeoutException();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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,14 +16,15 @@
package org.springframework.web.context.request.async;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Sends a 503 (SERVICE_UNAVAILABLE) in case of a timeout if the response is not
* already committed. Registered at the end, after all other interceptors and
* already committed. As of 4.2.8 this is done indirectly by returning
* {@link AsyncRequestTimeoutException} as the result of processing which is
* then handled by Spring MVC's default exception handling as a 503 error.
*
* <p>Registered at the end, after all other interceptors and
* therefore invoked only if no other interceptor handles the timeout.
*
* <p>Note that according to RFC 7231, a 503 without a 'Retry-After' header is
@@ -37,11 +38,8 @@ import org.springframework.web.context.request.NativeWebRequest;
public class TimeoutDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter {
@Override
public <T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
HttpServletResponse servletResponse = request.getNativeResponse(HttpServletResponse.class);
if (!servletResponse.isCommitted()) {
servletResponse.sendError(HttpStatus.SERVICE_UNAVAILABLE.value());
}
public <T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> result) throws Exception {
result.setErrorResult(new AsyncRequestTimeoutException());
return false;
}