关于Java:HttpServlet的AsyncContext没有响应

No response from HttpServlet with AsyncContext

我正在尝试在Tomcat上实现异步servlet,每次HttpSessionAttributeListener.attributeReplaced()触发时,它将向客户端发送更新。客户端配置为接收服务器发送事件。

尽管侦听器收到了更新,但浏览器未收到任何响应。浏览器的开发人员窗格显示,请求为pending,并且在AsyncContext.setTimeout()设置的超时后,错误为500。我没有想法,为什么会这样。

JS

1
2
3
4
5
6
var source = new EventSource('/acount/sse');

source.onmessage = function (event) {
    console.log(event.data);
    document.querySelector('#messageArea p').innerHTML += event.data;
};

这是我的servlet代码:

Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class SSE extends HttpServlet implements HttpSessionAttributeListener {

    public static final String ATTR_ENTRY_PROCESSOR_PROGRESS ="entryProcessorProgress";

    private AsyncContext aCtx;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

        resp.setContentType("text/event-stream");
        resp.setHeader("Cache-Control","no-cache");
        resp.setHeader("Connection","keep-alive");
        resp.setCharacterEncoding("UTF-8");

        aCtx = req.startAsync(req, resp);
        aCtx.setTimeout(80000);

    }

    @Override
    public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
        write(httpSessionBindingEvent);
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {

    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {
        write(httpSessionBindingEvent);
    }

    private void write(HttpSessionBindingEvent httpSessionBindingEvent) {
        if (httpSessionBindingEvent.getName().equals(ATTR_ENTRY_PROCESSOR_PROGRESS)) {
            try {
                String message ="data:" + httpSessionBindingEvent.getValue() +"\
\
"
;
                aCtx.getResponse().getWriter().write(message);
                aCtx.getResponse().getWriter().flush();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }


}

问题:

我尝试了您的代码,并且在以下位置出现了java.lang.NullPointerException:

1
 aCtx.getResponse().getWriter().write(message);

因为aCtx为空。

您已经混合使用了Servlet和Listener,但是在调用Listeners方法时,不会进行AsyncContext初始化。因此,什么也没有进入浏览器。

我在Servlet和侦听器中拆分了您的代码,并通过会话属性走私了AsychContext对象。因此可以在侦听器中访问它。

它有效。

完整代码:

HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
Server Event Listener
</head>
<body>




var source = new EventSource('/yourPath/ServerSentEvent');

source.onmessage = function (event) {
    console.log(event.data);
    document.querySelector('#messageArea p').innerHTML += event.data;
};

</body>
</html>

Servlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package testingThings.ServerSentEvent;

import java.io.IOException;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(asyncSupported = true, value = {"/ServerSentEvent"})
public class ServerSentEvent extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // add @WebServlet(asyncSupported = true) instead
        // http://stackoverflow.com/questions/7855712/how-to-avoid-request-set-async-supported-true-to-enable-async-servlet-3-0-proces
        // req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

        resp.setContentType("text/event-stream");
        resp.setHeader("Cache-Control","no-cache");
        resp.setHeader("Connection","keep-alive");
        resp.setCharacterEncoding("UTF-8");

        AsyncContext aCtx = req.startAsync(req, resp);
        aCtx.setTimeout(80000);

        // add a asyncContext a session Attribute
        req.getSession().setAttribute("asyncContext", aCtx);

        //start logging in listener
        req.getSession().setAttribute("entryProcessorProgress","trigger output");
    }
}

HttpSessionAttributeListener:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package testingThings.ServerSentEvent;

import java.io.IOException;

import javax.servlet.AsyncContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

@WebListener
public class SessionAttributeListener implements HttpSessionAttributeListener {

    public static final String ATTR_ENTRY_PROCESSOR_PROGRESS ="entryProcessorProgress";

    @Override
    public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
        write(httpSessionBindingEvent);
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {

    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {
        write(httpSessionBindingEvent);
    }

    private void write(HttpSessionBindingEvent httpSessionBindingEvent) {
        if (httpSessionBindingEvent.getName().equals(ATTR_ENTRY_PROCESSOR_PROGRESS)) {
            try {
                // get the AsyncContext from the session
                AsyncContext aCtx = (AsyncContext) httpSessionBindingEvent.getSession().getAttribute("asyncContext");
                String message ="data:" + httpSessionBindingEvent.getValue() +"\
\
"
;
                aCtx.getResponse().getWriter().write(message);
                aCtx.getResponse().getWriter().flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}