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:
@@ -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<?>} or
|
||||
* <li>An {@link org.springframework.http.HttpEntity HttpEntity<?>} or
|
||||
* {@link org.springframework.http.ResponseEntity ResponseEntity<?>} 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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user