Initial cut of Servlet 3.0 based async support

From a programming model perspective, @RequestMapping methods now
support two new return value types:

* java.util.concurrent.Callable - used by Spring MVC to obtain the
return value asynchronously in a separate thread managed transparently
by Spring MVC on behalf of the application.
* org.springframework.web.context.request.async.DeferredResult - used
by the application to produce the return value asynchronously in a
separate thread of its own choosing.

The high-level idea is that whatever value a controller normally
returns, it can now provide it asynchronously, through a Callable or
through a DeferredResult, with all remaining processing --
@ResponseBody, view resolution, etc, working just the same but
completed outside the main request thread.

From an SPI perspective, there are several new types:

* AsyncExecutionChain - the central class for managing async request
processing through a sequence of Callable instances each representing
work required to complete request processing asynchronously.
* AsyncWebRequest - provides methods for starting, completing, and
configuring async request processing.
* StandardServletAsyncWebRequest - Servlet 3 based implementation.
* AsyncExecutionChainRunnable - the Runnable used for async request
execution.

All spring-web and spring-webmvc Filter implementations have been
updated to participate in async request execution.
The open-session-in-view Filter and interceptors implementations in
spring-orm will be updated in a separate pull request.

Issue: SPR-8517
This commit is contained in:
Rossen Stoyanchev
2012-04-12 23:20:35 -04:00
parent fdded0768e
commit 3642b0f365
27 changed files with 2257 additions and 363 deletions

View File

@@ -21,6 +21,7 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.Callable;
/**
* Annotation for mapping web requests onto specific handler classes and/or
@@ -180,12 +181,18 @@ import java.lang.annotation.Target;
* be converted to the response stream using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
* converters}.
* <li>A {@link org.springframework.http.HttpEntity HttpEntity&lt;?&gt;} or
* <li>An {@link org.springframework.http.HttpEntity HttpEntity&lt;?&gt;} or
* {@link org.springframework.http.ResponseEntity ResponseEntity&lt;?&gt;} object
* (Servlet-only) to access to the Servlet response HTTP headers and contents.
* The entity body will be converted to the response stream using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message
* converters}.
* <li>A {@link Callable} which is used by Spring MVC to obtain the return
* value asynchronously in a separate thread transparently managed by Spring MVC
* on behalf of the application.
* <li>A {@code org.springframework.web.context.request.async.DeferredResult}
* which the application uses to produce a return value in a separate
* thread of its own choosing, as an alternative to returning a Callable.
* <li><code>void</code> if the method handles the response itself (by
* writing the response content directly, declaring an argument of type
* {@link javax.servlet.ServletResponse} / {@link javax.servlet.http.HttpServletResponse}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2012 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;
import java.util.concurrent.Callable;
/**
* A base class for a Callable that can be used in a chain of Callable instances.
*
* <p>Typical use for async request processing scenarios involves:
* <ul>
* <li>Create an instance of this type and register it via
* {@link AsyncExecutionChain#addDelegatingCallable(AbstractDelegatingCallable)}
* (internally the nodes of the chain will be linked so no need to set up "next").
* <li>Provide an implementation of {@link Callable#call()} that contains the
* logic needed to complete request processing outside the main processing thread.
* <li>In the implementation, delegate to the next Callable to obtain
* its result, e.g. ModelAndView, and then do some post-processing, e.g. view
* resolution. In some cases both pre- and post-processing might be
* appropriate, e.g. setting up {@link ThreadLocal} storage.
* </ul>
*
* @author Rossen Stoyanchev
* @since 3.2
*
* @see AsyncExecutionChain
*/
public abstract class AbstractDelegatingCallable implements Callable<Object> {
private Callable<Object> next;
public void setNextCallable(Callable<Object> nextCallable) {
this.next = nextCallable;
}
protected Callable<Object> getNextCallable() {
return this.next;
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright 2002-2012 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;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import javax.servlet.ServletRequest;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.Assert;
import org.springframework.web.context.request.async.DeferredResult.DeferredResultHandler;
/**
* The central class for managing async request processing, mainly intended as
* an SPI and typically not by non-framework classes.
*
* <p>An async execution chain consists of a sequence of Callable instances and
* represents the work required to complete request processing in a separate
* thread. To construct the chain, each layer in the call stack of a normal
* request (e.g. filter, servlet) may contribute an
* {@link AbstractDelegatingCallable} when a request is being processed.
* For example the DispatcherServlet might contribute a Callable that
* performs view resolution while a HandlerAdapter might contribute a Callable
* that returns the ModelAndView, etc. The last Callable is the one that
* actually produces an application-specific value, for example the Callable
* returned by an {@code @RequestMapping} method.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public final class AsyncExecutionChain {
public static final String CALLABLE_CHAIN_ATTRIBUTE = AsyncExecutionChain.class.getName() + ".CALLABLE_CHAIN";
private final List<AbstractDelegatingCallable> delegatingCallables = new ArrayList<AbstractDelegatingCallable>();
private Callable<Object> callable;
private AsyncWebRequest asyncWebRequest;
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("AsyncExecutionChain");
/**
* Private constructor
* @see #getForCurrentRequest()
*/
private AsyncExecutionChain() {
}
/**
* Obtain the AsyncExecutionChain for the current request.
* Or if not found, create an instance and associate it with the request.
*/
public static AsyncExecutionChain getForCurrentRequest(ServletRequest request) {
AsyncExecutionChain chain = (AsyncExecutionChain) request.getAttribute(CALLABLE_CHAIN_ATTRIBUTE);
if (chain == null) {
chain = new AsyncExecutionChain();
request.setAttribute(CALLABLE_CHAIN_ATTRIBUTE, chain);
}
return chain;
}
/**
* Provide an instance of an AsyncWebRequest.
* This property must be set before async request processing can begin.
*/
public void setAsyncWebRequest(AsyncWebRequest asyncRequest) {
this.asyncWebRequest = asyncRequest;
}
/**
* Provide an AsyncTaskExecutor to use when
* {@link #startCallableChainProcessing()} is invoked, for example when a
* controller method returns a Callable.
* <p>By default a {@link SimpleAsyncTaskExecutor} instance is used.
*/
public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
/**
* Whether async request processing has started through one of:
* <ul>
* <li>{@link #startCallableChainProcessing()}
* <li>{@link #startDeferredResultProcessing(DeferredResult)}
* </ul>
*/
public boolean isAsyncStarted() {
return ((this.asyncWebRequest != null) && this.asyncWebRequest.isAsyncStarted());
}
/**
* Add a Callable with logic required to complete request processing in a
* separate thread. See {@link AbstractDelegatingCallable} for details.
*/
public void addDelegatingCallable(AbstractDelegatingCallable callable) {
Assert.notNull(callable, "Callable required");
this.delegatingCallables.add(callable);
}
/**
* Add the last Callable, for example the one returned by the controller.
* This property must be set prior to invoking
* {@link #startCallableChainProcessing()}.
*/
public AsyncExecutionChain setCallable(Callable<Object> callable) {
Assert.notNull(callable, "Callable required");
this.callable = callable;
return this;
}
/**
* Start the async execution chain by submitting an
* {@link AsyncExecutionChainRunnable} instance to the TaskExecutor provided via
* {@link #setTaskExecutor(AsyncTaskExecutor)} and returning immediately.
* @see AsyncExecutionChainRunnable
*/
public void startCallableChainProcessing() {
startAsync();
this.taskExecutor.execute(new AsyncExecutionChainRunnable(this.asyncWebRequest, buildChain()));
}
private void startAsync() {
Assert.state(this.asyncWebRequest != null, "An AsyncWebRequest is required to start async processing");
this.asyncWebRequest.startAsync();
}
private Callable<Object> buildChain() {
Assert.state(this.callable != null, "The callable field is required to complete the chain");
this.delegatingCallables.add(new StaleAsyncRequestCheckingCallable(asyncWebRequest));
Callable<Object> result = this.callable;
for (int i = this.delegatingCallables.size() - 1; i >= 0; i--) {
AbstractDelegatingCallable callable = this.delegatingCallables.get(i);
callable.setNextCallable(result);
result = callable;
}
return result;
}
/**
* Mark the start of async request processing accepting the provided
* DeferredResult and initializing it such that if
* {@link DeferredResult#set(Object)} is called (from another thread),
* the set Object value will be processed with the execution chain by
* invoking {@link AsyncExecutionChainRunnable}.
* <p>The resulting processing from this method is identical to
* {@link #startCallableChainProcessing()}. The main difference is in
* the threading model, i.e. whether a TaskExecutor is used.
* @see DeferredResult
*/
public void startDeferredResultProcessing(DeferredResult deferredResult) {
Assert.notNull(deferredResult, "A DeferredResult is required");
startAsync();
deferredResult.setValueProcessor(new DeferredResultHandler() {
public void handle(Object value) {
if (asyncWebRequest.isAsyncCompleted()) {
throw new StaleAsyncWebRequestException("Async request processing already completed");
}
setCallable(getSimpleCallable(value));
new AsyncExecutionChainRunnable(asyncWebRequest, buildChain()).run();
}
});
}
private Callable<Object> getSimpleCallable(final Object value) {
return new Callable<Object>() {
public Object call() throws Exception {
return value;
}
};
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2002-2012 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;
import java.util.concurrent.Callable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
/**
* A Runnable for invoking a chain of Callable instances and completing async
* request processing while also dealing with any unhandled exceptions.
*
* @author Rossen Stoyanchev
* @since 3.2
*
* @see AsyncExecutionChain#startCallableChainProcessing()
* @see AsyncExecutionChain#startDeferredResultProcessing(DeferredResult)
*/
public class AsyncExecutionChainRunnable implements Runnable {
private static final Log logger = LogFactory.getLog(AsyncWebRequest.class);
private final AsyncWebRequest asyncWebRequest;
private final Callable<?> callable;
/**
* Class constructor.
* @param asyncWebRequest the async request
* @param callable the async execution chain
*/
public AsyncExecutionChainRunnable(AsyncWebRequest asyncWebRequest, Callable<?> callable) {
Assert.notNull(asyncWebRequest, "An AsyncWebRequest is required");
Assert.notNull(callable, "A Callable is required");
Assert.state(asyncWebRequest.isAsyncStarted(), "Not an async request");
this.asyncWebRequest = asyncWebRequest;
this.callable = callable;
}
/**
* Run the async execution chain and complete the async request.
* <p>A {@link StaleAsyncWebRequestException} is logged at debug level and
* absorbed while any other unhandled {@link Exception} results in a 500
* response code.
*/
public void run() {
try {
logger.debug("Starting async execution chain");
callable.call();
}
catch (StaleAsyncWebRequestException ex) {
logger.trace("Could not complete async request", ex);
}
catch (Exception ex) {
logger.trace("Could not complete async request", ex);
this.asyncWebRequest.sendError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
}
finally {
logger.debug("Exiting async execution chain");
asyncWebRequest.complete();
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2002-2012 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;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Extends {@link NativeWebRequest} with methods for starting, completing, and
* configuring async request processing. Abstract underlying mechanisms such as
* the Servlet 3.0 AsyncContext.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public interface AsyncWebRequest extends NativeWebRequest {
/**
* Set the timeout for asynchronous request processing. When the timeout
* begins depends on the underlying technology. With the Servlet 3 async
* support the timeout begins after the main processing thread has exited
* and has been returned to the container pool.
*/
void setTimeout(Long timeout);
/**
* Marks the start of async request processing for example. Ensures the
* request remains open to be completed in a separate thread.
*/
void startAsync();
/**
* Return {@code true} if async processing has started following a call to
* {@link #startAsync()} and before it has completed.
*/
boolean isAsyncStarted();
/**
* Complete async request processing finalizing the underlying request.
*/
void complete();
/**
* Send an error to the client.
*/
void sendError(HttpStatus status, String message);
/**
* Return {@code true} if async processing completed either normally or for
* any other reason such as a timeout or an error. Note that a timeout or a
* (client) error may occur in a separate thread while async processing is
* still in progress in its own thread. Hence the underlying async request
* may become stale and this method may return {@code true} even if
* {@link #complete()} was never actually called.
* @see StaleAsyncWebRequestException
*/
boolean isAsyncCompleted();
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2002-2012 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;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.util.Assert;
/**
* DeferredResult provides an alternative to using a Callable to complete async
* request processing. Whereas with a Callable the framework manages a thread on
* behalf of the application through an {@link AsyncTaskExecutor}, with a
* DeferredResult the application can produce a value using a thread of its choice.
*
* <p>The following sequence describes typical use of a DeferredResult:
* <ol>
* <li>Application method (e.g. controller method) returns a DeferredResult instance
* <li>The framework completes initialization of the returned DeferredResult in the same thread
* <li>The application calls {@link DeferredResult#set(Object)} from another thread
* <li>The framework completes request processing in the thread in which it is invoked
* </ol>
*
* <p><strong>Note:</strong> {@link DeferredResult#set(Object)} will block if
* called before the DeferredResult is fully initialized (by the framework).
* Application code should never create a DeferredResult and set it immediately:
*
* <pre>
* DeferredResult value = new DeferredResult();
* value.set(1); // blocks
* </pre>
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public final class DeferredResult {
private final AtomicReference<Object> value = new AtomicReference<Object>();
private final BlockingQueue<DeferredResultHandler> handlers = new ArrayBlockingQueue<DeferredResultHandler>(1);
/**
* Provide a value to use to complete async request processing.
* This method should be invoked only once and usually from a separate
* thread to allow the framework to fully initialize the created
* DeferrredValue. See the class level documentation for more details.
*
* @throws StaleAsyncWebRequestException if the underlying async request
* ended due to a timeout or an error before the value was set.
*/
public void set(Object value) throws StaleAsyncWebRequestException {
Assert.isNull(this.value.get(), "Value already set");
this.value.set(value);
try {
this.handlers.take().handle(value);
}
catch (InterruptedException e) {
throw new IllegalStateException("Failed to process deferred return value: " + value, e);
}
}
void setValueProcessor(DeferredResultHandler handler) {
this.handlers.add(handler);
}
/**
* Puts the set value through processing wiht the async execution chain.
*/
interface DeferredResultHandler {
void handle(Object result) throws StaleAsyncWebRequestException;
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2002-2012 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;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.request.ServletWebRequest;
/**
* An implementation of {@link AsyncWebRequest} used when no underlying support
* for async request processing is available in which case {@link #startAsync()}
* results in an {@link UnsupportedOperationException}.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class NoOpAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest {
public NoOpAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
super(request, response);
}
public void setTimeout(Long timeout) {
}
public boolean isAsyncStarted() {
return false;
}
public boolean isAsyncCompleted() {
return false;
}
public void startAsync() {
throw new UnsupportedOperationException("No async support in a pre-Servlet 3.0 runtime");
}
public void complete() {
throw new UnsupportedOperationException("No async support in a pre-Servlet 3.0 environment");
}
public void sendError(HttpStatus status, String message) {
throw new UnsupportedOperationException("No async support in a pre-Servlet 3.0 environment");
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2002-2012 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;
/**
* Invokes the next Callable in a chain and then checks if the AsyncWebRequest
* provided to the constructor has ended before returning. Since a timeout or a
* (client) error may occur in a separate thread while async request processing
* is still in progress in its own thread, inserting this Callable in the chain
* protects against use of stale async requests.
*
* <p>If an async request was terminated while the next Callable was still
* processing, a {@link StaleAsyncWebRequestException} is raised.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class StaleAsyncRequestCheckingCallable extends AbstractDelegatingCallable {
private final AsyncWebRequest asyncWebRequest;
public StaleAsyncRequestCheckingCallable(AsyncWebRequest asyncWebRequest) {
this.asyncWebRequest = asyncWebRequest;
}
public Object call() throws Exception {
Object result = getNextCallable().call();
if (this.asyncWebRequest.isAsyncCompleted()) {
throw new StaleAsyncWebRequestException(
"Async request no longer available due to a timed out or a (client) error");
}
return result;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2002-2012 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;
/**
* Raised if a stale AsyncWebRequest is detected during async request processing.
*
* @author Rossen Stoyanchev
* @since 3.2
*
* @see DeferredResult#set(Object)
* @see AsyncExecutionChainRunnable#run()
*/
@SuppressWarnings("serial")
public class StaleAsyncWebRequestException extends RuntimeException {
public StaleAsyncWebRequestException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2002-2012 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;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import org.springframework.web.context.request.ServletWebRequest;
/**
* A Servlet 3.0 implementation of {@link AsyncWebRequest}.
*
* <p>The servlet processing an async request as well as all filters involved
* must async support enabled. This can be done in Java using the Servlet API
* or by adding an {@code <async-support>true</async-support>} element to
* servlet and filter declarations in web.xml
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class StandardServletAsyncWebRequest extends ServletWebRequest implements AsyncWebRequest, AsyncListener {
private Long timeout;
private AsyncContext asyncContext;
private AtomicBoolean asyncCompleted = new AtomicBoolean(false);
public StandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
super(request, response);
}
public void setTimeout(Long timeout) {
this.timeout = timeout;
}
public boolean isAsyncStarted() {
assertNotStale();
return ((this.asyncContext != null) && getRequest().isAsyncStarted());
}
public boolean isAsyncCompleted() {
return this.asyncCompleted.get();
}
public void startAsync() {
Assert.state(getRequest().isAsyncSupported(),
"Async support must be enabled on a servlet and for all filters involved " +
"in async request processing. This is done in Java code using the Servlet API " +
"or by adding \"<async-supported>true</async-supported>\" to servlet and " +
"filter declarations in web.xml.");
assertNotStale();
Assert.state(!isAsyncStarted(), "Async processing already started");
this.asyncContext = getRequest().startAsync(getRequest(), getResponse());
this.asyncContext.addListener(this);
if (this.timeout != null) {
this.asyncContext.setTimeout(this.timeout);
}
}
public void complete() {
assertNotStale();
if (!isAsyncCompleted()) {
this.asyncContext.complete();
}
}
public void sendError(HttpStatus status, String message) {
try {
if (!isAsyncCompleted()) {
getResponse().sendError(500, message);
}
}
catch (IOException ioEx) {
// absorb
}
}
private void assertNotStale() {
Assert.state(!isAsyncCompleted(), "Cannot use async request after completion");
}
// ---------------------------------------------------------------------
// Implementation of AsyncListener methods
// ---------------------------------------------------------------------
public void onTimeout(AsyncEvent event) throws IOException {
this.asyncCompleted.set(true);
}
public void onError(AsyncEvent event) throws IOException {
this.asyncCompleted.set(true);
}
public void onStartAsync(AsyncEvent event) throws IOException {
}
public void onComplete(AsyncEvent event) throws IOException {
this.asyncCompleted.set(true);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2012 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.
@@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
@@ -31,6 +32,8 @@ import javax.servlet.http.HttpSession;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.util.WebUtils;
/**
@@ -51,6 +54,7 @@ import org.springframework.web.util.WebUtils;
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @see #beforeRequest
* @see #afterRequest
* @since 1.2.5
@@ -185,14 +189,22 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (isIncludePayload()) {
request = new RequestCachingRequestWrapper(request);
}
beforeRequest(request, getBeforeMessage(request));
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
chain.addDelegatingCallable(getAsyncCallable(request));
try {
filterChain.doFilter(request, response);
}
finally {
if (chain.isAsyncStarted()) {
return;
}
afterRequest(request, getAfterMessage(request));
}
}
@@ -278,6 +290,20 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
*/
protected abstract void afterRequest(HttpServletRequest request, String message);
/**
* Create a Callable to use to complete processing in an async execution chain.
*/
private AbstractDelegatingCallable getAsyncCallable(final HttpServletRequest request) {
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
getNextCallable().call();
afterRequest(request, getAfterMessage(request));
return null;
}
};
}
private static class RequestCachingRequestWrapper extends HttpServletRequestWrapper {
private final ByteArrayOutputStream bos = new ByteArrayOutputStream();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2012 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.
@@ -25,6 +25,9 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
/**
* Filter base class that guarantees to be just executed once per request,
* on any servlet container. It provides a {@link #doFilterInternal}
@@ -35,6 +38,7 @@ import javax.servlet.http.HttpServletResponse;
* is based on the configured name of the concrete filter instance.
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 06.12.2003
*/
public abstract class OncePerRequestFilter extends GenericFilterBean {
@@ -70,12 +74,18 @@ public abstract class OncePerRequestFilter extends GenericFilterBean {
filterChain.doFilter(request, response);
}
else {
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
chain.addDelegatingCallable(getAsyncCallable(request, alreadyFilteredAttributeName));
// Do invoke this filter...
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
if (chain.isAsyncStarted()) {
return;
}
// Remove the "already filtered" request attribute for this request.
request.removeAttribute(alreadyFilteredAttributeName);
}
@@ -111,6 +121,20 @@ public abstract class OncePerRequestFilter extends GenericFilterBean {
return false;
}
/**
* Create a Callable to use to complete processing in an async execution chain.
*/
private AbstractDelegatingCallable getAsyncCallable(final ServletRequest request,
final String alreadyFilteredAttributeName) {
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
getNextCallable().call();
request.removeAttribute(alreadyFilteredAttributeName);
return null;
}
};
}
/**
* Same contract as for <code>doFilter</code>, but guaranteed to be

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2012 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.
@@ -17,6 +17,7 @@
package org.springframework.web.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -25,6 +26,8 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
/**
* Servlet 2.3 Filter that exposes the request to the current thread,
@@ -40,6 +43,7 @@ import org.springframework.web.context.request.ServletRequestAttributes;
*
* @author Juergen Hoeller
* @author Rod Johnson
* @author Rossen Stoyanchev
* @since 2.0
* @see org.springframework.context.i18n.LocaleContextHolder
* @see org.springframework.web.context.request.RequestContextHolder
@@ -74,17 +78,19 @@ public class RequestContextFilter extends OncePerRequestFilter {
throws ServletException, IOException {
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
RequestContextHolder.setRequestAttributes(attributes, this.threadContextInheritable);
if (logger.isDebugEnabled()) {
logger.debug("Bound request context to thread: " + request);
}
initContextHolders(request, attributes);
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
chain.addDelegatingCallable(getChainedCallable(request, attributes));
try {
filterChain.doFilter(request, response);
}
finally {
LocaleContextHolder.resetLocaleContext();
RequestContextHolder.resetRequestAttributes();
resetContextHolders();
if (chain.isAsyncStarted()) {
return;
}
attributes.requestCompleted();
if (logger.isDebugEnabled()) {
logger.debug("Cleared thread-bound request context: " + request);
@@ -92,4 +98,41 @@ public class RequestContextFilter extends OncePerRequestFilter {
}
}
private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
if (logger.isDebugEnabled()) {
logger.debug("Bound request context to thread: " + request);
}
}
private void resetContextHolders() {
LocaleContextHolder.resetLocaleContext();
RequestContextHolder.resetRequestAttributes();
}
/**
* Create a Callable to use to complete processing in an async execution chain.
*/
private AbstractDelegatingCallable getChainedCallable(final HttpServletRequest request,
final ServletRequestAttributes requestAttributes) {
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
initContextHolders(request, requestAttributes);
try {
getNextCallable().call();
}
finally {
resetContextHolders();
requestAttributes.requestCompleted();
if (logger.isDebugEnabled()) {
logger.debug("Cleared thread-bound request context: " + request);
}
}
return null;
}
};
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2012 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.
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
@@ -30,6 +31,8 @@ import javax.servlet.http.HttpServletResponseWrapper;
import org.springframework.util.DigestUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.context.request.async.AbstractDelegatingCallable;
import org.springframework.web.context.request.async.AsyncExecutionChain;
import org.springframework.web.util.WebUtils;
/**
@@ -41,6 +44,7 @@ import org.springframework.web.util.WebUtils;
* is still rendered. As such, this filter only saves bandwidth, not server performance.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.0
*/
public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
@@ -55,8 +59,37 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
throws ServletException, IOException {
ShallowEtagResponseWrapper responseWrapper = new ShallowEtagResponseWrapper(response);
AsyncExecutionChain chain = AsyncExecutionChain.getForCurrentRequest(request);
chain.addDelegatingCallable(getAsyncCallable(request, response, responseWrapper));
filterChain.doFilter(request, responseWrapper);
if (chain.isAsyncStarted()) {
return;
}
updateResponse(request, response, responseWrapper);
}
/**
* Create a Callable to use to complete processing in an async execution chain.
*/
private AbstractDelegatingCallable getAsyncCallable(final HttpServletRequest request,
final HttpServletResponse response, final ShallowEtagResponseWrapper responseWrapper) {
return new AbstractDelegatingCallable() {
public Object call() throws Exception {
getNextCallable().call();
updateResponse(request, response, responseWrapper);
return null;
}
};
}
private void updateResponse(HttpServletRequest request, HttpServletResponse response,
ShallowEtagResponseWrapper responseWrapper) throws IOException {
byte[] body = responseWrapper.toByteArray();
int statusCode = responseWrapper.getStatusCode();
@@ -149,6 +182,7 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter {
this.statusCode = sc;
}
@SuppressWarnings("deprecation")
@Override
public void setStatus(int sc, String sm) {
super.setStatus(sc, sm);

View File

@@ -106,8 +106,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
* a thrown exception instance. Provided argument values are checked before argument resolvers.
*
* @param request the current request
* @param mavContainer the {@link ModelAndViewContainer} for the current request
* @param providedArgs argument values to try to use without view resolution
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type, not resolved
* @return the raw value returned by the invoked method
* @exception Exception raised if no suitable argument resolver can be found, or the method raised an exception
*/