Skip to main content

Server-Sent Events with Async Servlet By Example

Posted by swchan2 on May 21, 2014 at 3:57 PM PDT

Server-Sent Events (SSE) is part of HTML5. SSE is a simple, undirectional communication from server to browser. It allows server to push data to client once a connection is established. The entire point of SSE is to make it easy for the server to push messages to the browser, once the browser has first established a connection to the server. These messages are of the form "field: value" with each message separated by a newline. The field in the messages could be one of the following: event, data, id, retry.
On the client side JavaScript, the EventSource interface is used to process SSE. And it is supported by most of the popular browsers.

In this blog entry, we will illustrate how to use SSE with Servlets by example. In this first example, we will have a very simple Servlet sending an SSE. In the second example, we will show a Servlet sending SSE using the Servlet 3.0 Async API and Java EE 7 Concurrency Utilities.

Example 1

We will look at a very simple Servlet sending a SSE. There are two steps:

  1. set the content type to text/event-stream
  2. write the SSE data (remember the last new line!)

The code is as follows. Please see comments inside the code.

@WebServlet(urlPatterns={"/simplesse"})
public class SSEEchoServlet extends HttpServlet {

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

        // set content type
        res.setContentType("text/event-stream");
        res.setCharacterEncoding("UTF-8");

        String msg = req.getParameter("msg");

        PrintWriter writer = res.getWriter();

        // send SSE
        writer.write("data: " + msg + "\n\n");
    }
}

The client side JavaScript will setup a EventSource as follows:

<html>
  <body>
    <script>
      function setupEventSource() {
        var output = document.getElementById("output");
        if (typeof(EventSource) !== "undefined") {
          var msg = document.getElementById("textID").value;
          var source = new EventSource("simplesse?msg=" + msg);
          source.onmessage = function(event) {
            output.innerHTML += event.data + "<br>";
          };
        } else {
          output.innerHTML = "Sorry, Server-Sent Event is not supported in your browser";
        }
        return false;
      }
    </script>

    <h2>Simple SSE Echo Demo</h2>
    <div>
      <input type="text" id="textID" name="message" value="Hello World">
      <input type="button" id="sendID" value="Send" onclick="setupEventSource()"/>
    </div>
    <hr/>
    <div id="output"></div>
  </body>
</html>

In this example, the client will reconnect automatically.

Example 2

It is very common for server to push data to the client asynchronously. In this example, Servlet Async API and Java EE concurrent Utitlities are used to push data asynchronously.

// enable async in the servlet
@WebServlet(urlPatterns={"/sseasync"}, asyncSupported=true)
public class SSEAsyncServlet extends HttpServlet {
    @Resource
    private ManagedExecutorService managedExecutorService;

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

        // set content type
        res.setContentType("text/event-stream");
        res.setCharacterEncoding("UTF-8");

        final String msg = req.getParameter("msg");

        // start async
        final AsyncContext ac = req.startAsync();

        final PrintWriter writer = res.getWriter();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // echo msg 5 times
                for (int i = 0; i < 5; i++) {
                    if (i == 4) { // last
                        // SSE event field
                        writer.write("event: close\n");
                    }
                    // SSE data field
                    // last field with blank new line
                    writer.write("data: " + msg + "\n\n");
                    writer.flush();

                    try {
                        Thread.sleep(2000);
                    } catch(InterruptedException iex) {
                        iex.printStackTrace();
                    }
                }

                // complete async
                ac.complete();
            }
        };

        // submit the runnable to managedExecutorService
        managedExecutorService.submit(runnable);
    }
}

In above example, the last SSE has a event name, close. This information is used on the client side. A special handler is registered for the event with name close. The JavaScript is as follows:

<html>
  <body>
    <script>
      function setupEventSource() {
        var output = document.getElementById("output");
        if (typeof(EventSource) !== "undefined") {
          var msg = document.getElementById("textID").value;
          var source = new EventSource("sseasync?msg=" + msg);
          source.onmessage = function(event) {
            output.innerHTML += event.data + "<br>";
          };
          source.addEventListener('close', function(event) {
            output.innerHTML += event.data + "<hr/>";
            source.close();
            }, false);
        } else {
          output.innerHTML = "Sorry, Server-Sent Events are not supported in your browser";
        }
        return false;
      }
    </script>

    <h2>SSE Echo Demo</h2>
    <div>
      <input type="text" id="textID" name="message" value="Hello World">
      <input type="button" id="sendID" value="Send" onclick="setupEventSource()"/>
    </div>
    <hr/>
    <div id="output"></div>
  </body>
</html>     

Note that source.close() is invoked so that the client will not reconnect to server after close event.

Comments

So, not sure if it's the expected behaviour but once I click ...

So, not sure if it's the expected behaviour but once I click submit, the SSE seems to loop; i.e. it continually sends back the message. Adding more text and clicking submit again seems to start another thread with both messages being echoed back on a loop. Is this correct?

I'm using Safari 7.0.5 on the client side and Tomcat 8.0.9 with Java 8u11