简体   繁体   中英

Cannot forward after response has been committed when using deferredResult

In order to implement long polling I've tried different solution and did not acquire any good result.

So I decide to look into asynchronous methods and DeferredResult . Here I implemented REST constroller.

@Controller("sessionStateRest")
@RequestMapping("ui")
public class SessionStateRest extends BaseRestResource {

    private final Queue<DeferredResult<ModelAndView>> mavQueue = new ConcurrentLinkedQueue<>();

    /**
     * Rest to check session state.
     *
     * @return string with session state
     */
    @RequestMapping(value = "/session")
    public @ResponseBody DeferredResult<ModelAndView> sessionState() {
        final DeferredResult<ModelAndView> stateResult = new DeferredResult<>();
        this.mavQueue.add(stateResult);
        return stateResult;
    }

    @Scheduled(fixedDelay = 5000)
    public void processQueue() {
        for(DeferredResult<ModelAndView> result: mavQueue) {
            if (null == SecurityHelper.getUserLogin()) {
                result.setResult(createSuccessResponse("Invalidated session"));
                mavQueue.remove(result);
            }
        }
    }
}

By idea it should process queue of requests every 5 seconds and setResult if condition is true.

Synchronous version would be something like this

@RequestMapping(value = "/sync")
public ModelAndView checkState() {
    if (null == SecurityHelper.getUserLogin()) {
        createSuccessResponse("Invalidated session");
    }
    return null; // return something instead
}

But after some time I've got an exception

java.lang.IllegalStateException: Cannot forward after response has been committed
        at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:349) ~[tomcat-embed-core-7.0.
39.jar:7.0.39]
        at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:339) ~[tomcat-embed-core-7.0.39
.jar:7.0.39]
        at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:467) [tomcat-embed-core-7.0.39.jar:7.0.3
9]
        at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:338) [tomcat-embed-core-7.0.39.jar:7.0.3
9]
        at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:428) [tomcat-embed-core-7.0.39.jar:7.
0.39]
        at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:417) [tomcat-embed-core-7.0.39.jar:
7.0.39]
        at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:294) [tomcat-embed-core-7.0.39.jar:7
.0.39]
        at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1567) [tomcat-embed-c
ore-7.0.39.jar:7.0.39]
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:583) [tomcat-embed-cor
e-7.0.39.jar:7.0.39]
        at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312) [tomcat-embed-core-7.0.39.jar:7.
0.39]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_67]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_67]
        at java.lang.Thread.run(Thread.java:745) [na:1.7.0_67]

What's the problem? Should I set the timeout for DeferredResult ?

I think the problem comes from the @ResponseBody annotation. It tells Spring that the controller method will directly returns the body of the response. But it does not, because it returns a ModelAndView . So Spring tries to send the return of the method directly to client, (and should send and commit an empty response), then the ModelAndView handler tries to forward to a view with an already committed response causing the error.

You should at least remove the @ResponseBody annotation since it is not it what would be a synchronous equivalent.

But that's not all :

  • you write final DeferredResult<... - IMHO the final should not be there since you will modify later the DeferredResult
  • you try to test logged user in the scheduled asynchronous thread. This should not work, since common SecurityHelper use local thread storage to store this info, and actual processing will occur in another thread. Javadoc for DeferredResult even says : For example, one might want to associate the user used to create the DeferredResult by extending the class and adding an addition property for the user. In this way, the user could easily be accessed later without the need to use a data structure to do the mapping.
  • you do not say how you configured async support. Spring Reference manual says : The MVC Java config and the MVC namespace both provide options for configuring async request processing. WebMvcConfigurer has the method configureAsyncSupport while <mvc:annotation-driven> has an <async-support> sub-element.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM