19

I tried an SSE (Server-Sent-Events) using java on tomcat 8.0. Here are few things I noticed.

I click a button that automatically makes a request to the servlet. Servlet's GET method gets executed which returns an event stream. Once the full stream is received, the page again automatically makes another request which receives the same data again!!! I don't have an infinite loop there!!!

  1. What is actually happening on the server? In normal scenarios, tomcat creates a thread to handle every request. What is happening now?

  2. What is the correct way to ensure that the event stream is sent only once to the same connection/browser session?

  3. What is the correct way to ensure that the event stream is closed and no resource overhead incurs on the server?

  4. How to differentiate between GET and POST requests. Why did it choose GET?

  5. Is it too early to use SSE on Tomcat? Any performance issues?

Here is the code for the curious,

@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {
 public void doGet(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 //content type must be set to text/event-stream
 response.setContentType("text/event-stream"); 
 //cache must be set to no-cache
 response.setHeader("Cache-Control", "no-cache"); 
 //encoding is set to UTF-8
 response.setCharacterEncoding("UTF-8");
 PrintWriter writer = response.getWriter();
 for(int i=0; i<10; i++) {
 System.out.println(i);
 writer.write("data: "+ i +"\n\n");
 writer.flush();
 try {
 Thread.sleep(3000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 writer.close(); 
 }
}

Javascript on the page (I don't have anything else on the page),

<button onclick="start()">Start</button>
<script type="text/javascript">
 function start() {
 var eventSource = new EventSource("TestServlet");
 eventSource.onmessage = function(event) {
 console.log("data: "+event.data)
 document.getElementById('foo').innerHTML = event.data;
 };
 }
</script>

Tried this using CURL. And the response came just once. I'm using chrome, so this must be a issue with chorme??

EDIT:

What I have learned and learning is now documented in my blog - Server Sent Events

asked Aug 2, 2015 at 3:43
6
  • It might be your browser. try to send the initial request using curl and see if it still happens. Commented Aug 2, 2015 at 3:46
  • Yes you are right. Curl stopped with one request. Commented Aug 2, 2015 at 3:55
  • I fell for the same one... at least I was able to save you the time & frustration ;) Commented Aug 2, 2015 at 3:55
  • 1
    @John I don't think its problem with browser. I am using such think from last 6-8 months and it's working fine on production. Check with your script may be start() is getting called multiple times? Commented Aug 2, 2015 at 6:00
  • @Amogh But how is that possible? I commented the code and put a console.log and it is getting called just once when I click the button. If you have been using this for months then you might have answers to my question. Commented Aug 2, 2015 at 13:22

3 Answers 3

24

Change this line

writer.write("data: "+ i +"\n\n");

to

writer.write("data: "+ i +"\r\n");

BTW, your code will have a serious performance issue because it will hold a thread until all events are sent.Please use Asynchronous processing API instead. e.g.

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 AsyncContext actx = req.startAsync();
 actx.setTimeout(30*1000);
 //save actx and use it when we need sent data to the client.
}

Then we can use AsyncContext later

//write some data to client when a certain event happens
actx.getResponse().getWriter().write("data: " + mydata + "\r\n");
actx.getResponse().getWriter().flush();

if all events sent we can close it

actx.complete();

UPDATE 1:

We need close the event source at browser if we do not want browser reconnect the server again when server completes the response.

eventSource.close();

Another method maybe helps, viz. we set a quite large retry time but I have not tried it, e.g.

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 AsyncContext actx = req.startAsync();
 actx.getResponse().getWriter().write("retry: 36000000000\r\n"); // 10000 hours!
 actx.getResponse().getWriter().flush();
 //save actx and use it when we need sent data to the client.
}

UPDATE 2:

I think Websocket maybe is better for your case.

UPDATE 3: (answer the questions)

  1. What is actually happening on the server? In normal scenarios, tomcat creates a thread to handle every request. What is happening now?

If use NIO connector which is default in Tomcat 8.0.X, within the whole processing cycle HTTP I/O about a request won't hold a thread. If use BIO a thread will be hold until the whole processing cycle completes. All threads are from a thread pool, tomcat won't create a thread for each request.

  1. What is the correct way to ensure that the event stream is sent only once to the same connection/browser session?

Do eventSource.close() at browser side is the best choice.

  1. What is the correct way to ensure that the event stream is closed and no resource overhead incurs on the server?

Do not forget to invoke AsyncContext.complete() at server side.

  1. How to differentiate between GET and POST requests. Why did it choose GET?

The EventSource API in a browser only supports GET requests but at the server side there 's no such restriction. SSE is mainly used to receive events data from server. If a event happens the browser can receive it in time and no need to create a new request to poll it. If you need full-duplex communication try WebSocket instread of SSE.

  1. Is it too early to use SSE on Tomcat? Any performance issues?

There should be no performance issues if we use NIO connector & Asynchronous processing API. I don't know whether Tomcat NIO connector is mature or not but something will never be known unless we try it.

answered Aug 3, 2015 at 1:55
Sign up to request clarification or add additional context in comments.

5 Comments

Isn't closing a much better approach than the large retry time? Isn't it possible to send POST requests?
Closing is better. About GET or POST there's no restriction at server side.But according to the [W3C SPEC](w3.org/TR/eventsource ) browser will always use GET instead of POST and there 's no option to enable POST.
Now that's an answer. Thanks.
According to the spec: "Clients will reconnect if the connection is closed; a client can be told to stop reconnecting using the HTTP 204 No Content response code." I've been working on Java impl of SSE by my self, you can check it here: github.com/metteo/event-source
Spring Boot has Server-Sent Events when returning an SseEmitter on a Controller.
2

I highly recommend first to read Stream Updates with Server-Sent Events to get a good general understanding of the technology. Then follow Server-Sent Events with Async Servlet By Example to see how SSE can be used specifically with the Servlet technology.

answered Aug 3, 2015 at 3:59

3 Comments

Wtf is that on the second link? Does the person who wrote this belive this code is readable by humans? If so and he works for Oracle he should be fired on the spot.
@kuhaku Looks like something was changed in the underlying blog engine that affected the code formatting -- it was not like that before (:
Second link is broken, found it in the waybackmachine: web.archive.org/web/20150910123522/https://weblogs.java.net/…
0

The browser attempts to reconnect to the source roughly 3 seconds after each connection is closed. You can change that timeout by including a line beginning with "retry:", followed by the number of milliseconds to wait before trying to reconnect.

answered Dec 3, 2020 at 7:52

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.