AsyncContext响应与原始传入请求不匹配?

时间:2014-12-30 19:08:57

标签: java spring spring-mvc java-ee asynchronous

我们有一个带有仪表板的Web应用程序,该仪表板不断轮询更新。在服务器端,更新请求是异步的,以便我们可以在通过侦听器/通知系统进行更新时进行响应。

我们看到的问题是,当其中一个轮询请求被响应时,它可以在某些情况下写入用户点击链接的请求/响应。

异步更新的传入请求如下所示:

@RequestMapping("/getDashboardStatus.json")
public void getDashboardStatus(HttpServletRequest request, ...) {
    final AsyncContext asyncContext = request.startAsync();
    // 10 seconds
    asyncContext.setTimeout(10000);
    asyncContext.start(new Runnable() {
        public void run() {
            // .. (code here waits for an update to occur) ..
            sendMostRecentDashboardJSONToResponse(asyncContext.getResponse());
            if (asyncContext.getRequest().isAsyncStarted()) {
                asyncContext.complete();
            }
        }
    });
}

什么是奇怪的,这个仪表板上有链接到其他页面。每次约100次点击,其中一个将而不是显示所选页面,实际显示上面发送的JSON!

例如,我们有一个单独的MVC方法:

@RequestMapping("/result/{resultId}")
public ModelAndView getResult(@PathVariable String resultId) {
    return new ModelAndView(...);
}

当点击访问/result/1234的每个蓝月亮的仪表板上的链接时,页面将加载200 OK状态,但实际上不包含预期的HTML,而是包含轮询请求的JSON !

每个客户只允许一个请求吗?由点击的链接发起的请求是否覆盖已经位于服务器端的同一客户端的任何异步请求?

我们如何管理这些请求以确保异步响应转到异步请求?

我在hasOriginalRequestAndResponse()对象上注意到AsyncContext方法,但我很难理解Javadoc是否正是我正在寻找的。

更新:我刚刚添加了一个代码段:

String requestURI = ((HttpServletRequest)asyncContext.getRequest()).getRequestURI());
System.out.println("Responding w/ Dashboard to: " + requestURI);
sendMostRecentDashboardJSONToResponse(asyncContext.getResponse(), clientProfileKey);

并且能够重现这个问题,在正确行为期间,我看到了:

Responding w/ Dashboard to: /app/getDashboardStatus.json

但是当我看到JSON推送到点击启动的请求时,我看到:

Responding w/ Dashboard to: null

2 个答案:

答案 0 :(得分:5)

我已经把这个想出来了。请求/响应 确实正在被回收,因此通过挂起分配给AsyncContext的响应,我写出了与不同相关的响应em>请求。

调用startAsync()可确保在异步上下文完成之前不会回收请求/响应对象。尽管我发现上下文不会过早或错误地完成,但 已完成:

超时。

我能够通过等待10秒以上没有服务器端活动来持续重现此问题,允许JSON更新请求超时,然后单击链接。超时后,与异步上下文关联的请求/响应超时,因此已完成,因此已回收

我发现了两个解决方案

第一个是向上下文添加AsyncListener,并跟踪超时是否已发生。当您的侦听器检测到超时时,您可以翻转boolean,并在写入响应之前进行检查。

第二个是在写入响应之前简单地在请求上调用isAsyncStarted()。如果上下文超时,则此方法将返回false。如果上下文仍然有效/等待,它将返回true

答案 1 :(得分:0)

如果您阅读了Spring参考文档中的Asynchronous Request Processing章节,您会发现可以简化解决方案:

@RequestMapping(value = "/getDashboardStatus.json", 
                produces = MediaType.APPLICATION_JSON_VALUE)
public Callable<MostRecentDashboard> getDashboardStatus() {
    return new Callable() {
        @Override
        public MostRecentDashboard call() {
            // .. (code here waits for an update to occur) ..
            return ...;
        }
    });
}

MostRecentDashboard实例将使用Jackson序列化(假设Jackson 2在类路径上)。

接下来,您需要TaskExecutor来执行Callable(它也常用于配置默认超时),请阅读Spring MVC Async Config段以了解选项。或者,如果Spring不知道指定DeferredResult<MostRecentDashboard>值的线程,则可以返回MostRecentDashboard。从JMS,Redis或类似地方读取MostRecentDashboard个实例。有关更多信息,请阅读Introducing Servlet 3, Async Support博文。

Spring version 4.1开始,您可以直接从控制器返回ListenableFuture<MostRecentDashboard>,如果您使用MostRecentDashboard获取AsyncRestTemplate,这非常有用。