Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SSE and Servlet 3.0

I have registered a typical SSE when page loads:

Client:

sseTest: function(){

var source = new EventSource('mySSE');
source.onopen = function(event){
console.log("eventsource opened!");
};

source.onmessage = function(event){
var data = event.data;
console.log(data);
document.getElementById('sse').innerHTML+=event.data + "<br />";
};
}

My Javascript-Debugger says, that "eventsource opened!" was successfully.

My Server Code is a Servlet 3.0:

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns={"/mySSE"}, name = "hello-sse", asyncSupported=true)
public class MyServletSSE extends HttpServlet {

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

resp.setContentType("text/event-stream");
resp.setCharacterEncoding("UTF-8");

Random random = new Random();
PrintWriter out = resp.getWriter();

//AsyncContext aCtx = req.startAsync(req, resp);
//ServletRequest sReq = aCtx.getRequest();

String next = "data: " + String.valueOf(random.nextInt(100) + 1) + "\n\n";
//out.print("retry: 600000\n"); //set the timeout to 10 mins in milliseconds
out.write(next);
out.flush();
// do not close the stream as EventSource is listening
//out.close();
//super.doGet(req, resp);
}
}

The code works! The Client-Code triggers the doGet()-Method every 3 seconds and retrieves the new data.

Questions: However, I wonder how I can make this code better by using new Servlet 3.0 Futures such as Async-Support or asyncContext.addListener(asyncListener) or something else which I do not know. As I never closes the stream, I wonder how my server will scale?

Theoretically, the best approach would be to trigger the doGet()-Method via server-side-code explicitly when new data is there, so the client does not need to trigger the client-side "onmessage()"-Method and therefore the server side "doGet()"-Method every 3 seconds for new data.

like image 554
nimo23 Avatar asked Jun 04 '12 08:06

nimo23


People also ask

What is SSI in servlet?

Server-side includes are instructions and directives included in a web page that the web server may analyze when the page is provided. SSI refers to the servlet code that is embedded into the HTML code.

Is servlet deprecated?

It is not deprecated. They are still releasing new features on the latest Servlet (like async Servlet). And many of the Java web frameworks are building on top the the Servlet technology.

What is the latest version of servlet?

The current version of Servlet is 5.0.

Can we use both JSP and servlet?

The Model 2 architecture, as shown in Figure 3, integrates the use of both servlets and JSP pages. In this mode, JSP pages are used for the presentation layer, and servlets for processing tasks. The servlet acts as a controller responsible for processing requests and creating any beans needed by the JSP page.


1 Answers

This is an excellent question, here is a full working example (Servlet 3.0 / Java EE 6)

Some notes:

  1. it handles "browser tab / window closed" via out.checkError() that also calls flush()
  2. I wrote it quickly, so I'm sure it can be improved, just a POC, don't use in production before testing

Servlet: (omitted imports for brevity, will update a full gist soon)

@WebServlet(urlPatterns = {"/mySSE"}, asyncSupported = true)
public class MyServletSSE extends HttpServlet {

  private final Queue<AsyncContext> ongoingRequests = new ConcurrentLinkedQueue<>();
  private ScheduledExecutorService service;

  @Override
  public void init(ServletConfig config) throws ServletException {
    final Runnable notifier = new Runnable() {
      @Override
      public void run() {
        final Iterator<AsyncContext> iterator = ongoingRequests.iterator();
        //not using for : in to allow removing items while iterating
        while (iterator.hasNext()) {
          AsyncContext ac = iterator.next();
          Random random = new Random();
          final ServletResponse res = ac.getResponse();
          PrintWriter out;
          try {
            out = res.getWriter();
            String next = "data: " + String.valueOf(random.nextInt(100) + 1) + "num of clients = " + ongoingRequests.size() + "\n\n";
            out.write(next);
            if (out.checkError()) { //checkError calls flush, and flush() does not throw IOException
              iterator.remove();
            }
          } catch (IOException ignored) {
            iterator.remove();
          }
        }
      }
    };
    service = Executors.newScheduledThreadPool(10);
    service.scheduleAtFixedRate(notifier, 1, 1, TimeUnit.SECONDS);
  }

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse res) {
    res.setContentType("text/event-stream");
    res.setCharacterEncoding("UTF-8");

    final AsyncContext ac = req.startAsync();
    ac.setTimeout(60 * 1000);
    ac.addListener(new AsyncListener() {
      @Override public void onComplete(AsyncEvent event) throws IOException {ongoingRequests.remove(ac);}
      @Override public void onTimeout(AsyncEvent event) throws IOException {ongoingRequests.remove(ac);}
      @Override public void onError(AsyncEvent event) throws IOException {ongoingRequests.remove(ac);}
      @Override public void onStartAsync(AsyncEvent event) throws IOException {}
    });
    ongoingRequests.add(ac);
  }
}

JSP:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
        <script>
            function test() {
                var source = new EventSource('mySSE');
                source.onopen = function(event) {
                    console.log("eventsource opened!");
                };

                source.onmessage = function(event) {
                    var data = event.data;
                    console.log(data);
                    document.getElementById('sse').innerHTML += event.data + "<br />";
                };
            }
            window.addEventListener("load", test);
        </script>
    </head>
    <body>
        <h1>Hello SSE!</h1>
        <div id="sse"></div>
    </body>
</html>
like image 188
Eran Medan Avatar answered Sep 27 '22 17:09

Eran Medan