0

I'm trying to implement async servlet on Tomcat, that will send update to client every time an HttpSessionAttributeListener.attributeReplaced() is triggered. Client side is configured to receive Server Sent Events.

Although the listener receives the updates, the browser does not receive any response. Browser's developer pane shows, that the request is pending and it ends with error 500 after the timeout set by the AsyncContext.setTimeout(). I run out of ideas, why this is happening.

JS

var source = new EventSource('/acount/sse');

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

And this is my servlet code:

Servlet

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() + "\n\n";
                aCtx.getResponse().getWriter().write(message);
                aCtx.getResponse().getWriter().flush();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }


}

1 Answers1

0

The problem:

I tried your code and there was a java.lang.NullPointerException in:

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

Because the aCtx is null.

You have mixed the Servlet and Listener, but when the Listeners methods are called, the AsyncContext initialization is not. Therefore nothings go to the browser.

I splitted your code in Servlet an Listener and smuggled the AsychContext object through a session attribute. So it was accessible in the listener.

And it works.

The Complete Code:

The HTML:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Server Event Listener</title>
</head>
<body>
<div id="messageArea">
<p></p>
</div>
<script>
var source = new EventSource('/yourPath/ServerSentEvent');

source.onmessage = function (event) {
    console.log(event.data);
    document.querySelector('#messageArea p').innerHTML += event.data;
};
</script>
</body>
</html>

The Servlet:

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");
    }
}

The HttpSessionAttributeListener:

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() + "<br>\n\n";
                aCtx.getResponse().getWriter().write(message);
                aCtx.getResponse().getWriter().flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
code_angel
  • 1,537
  • 1
  • 11
  • 21