blogger templates blogger widgets
This is part of a list of blog posts.
To browse the contents go to

Server Sent Events


Server Sent Events enables efficient server-to-client streaming of text-based event data.

Ya, that's the single line definition. But it does explain quite a lot.

Server-to-Client
Yay!!!. Now this is a Comet technique. Well, not exactly but yes this is uni-directional from server to client.
Streaming
This is no new technology per se. It works on XHR Streaming behind the scenes. It works only on HTTP GET method However, unlike dealing XHR streaming on our own, the browser handles all the connection management and message parsing.
Text-Based
This is useful only for text based data. Using this for binary encoded base64 data is not efficient.
Event Data
SSE is amazing for event based data but you could also use it for non-event based. But hey it says Server Sent EVENTS.

SSE is most useful when user doesn't need to interact/modify the incoming events/messages.


To meet this goal, SSE introduces two components:
>> EventSource API interface in the browser, which allows the client to receive push notifications from the server as DOM events, and
>> EventStream data format, which is used to deliver the individual updates.

Note that no additional server side technology is need. Your async servlets just needs to send data in the "event stream" data format.

To know about Async servlets check this out - Async Handling using Servlets 3.0

EventSource API

The EventSource interface abstracts all the low-level connection establishment and message parsing behind a simple browser API.

var source = new EventSource("/path/to/stream-url");
source.addEventListener("foo", function (event) { 
processFoo(event.data);
});

Features:
1. There are 3 built in events - message, error, open. There is not close event. You can define custom events as seen above.
2. Provides auto-reconnect and tracking of the last seen message


EventStream Data format

Once client initiates a regular HTTP request, the server responds with a custom "text/event-stream" content-type, and then streams the UTF-8 encoded event data.

Few examples:

data: First message is a simple string.

event: hellomsg
data: First message is a simple string.

id: 23
event: foobar
data: {"message": "JSON payload"}

This is the data format criteria:
1. Event payload is the value of one or more adjacent data fields.
2. Event may carry an optional ID and an event type string.
3. Event boundaries are marked by newlines.

On the receiving end, the EventSource interface parses the incoming stream by looking for newline separators, extracts the payload from data fields, checks for optional ID and type, and finally dispatches a DOM event to notify the application. If a type is present, then a custom DOM event is fired, and otherwise the generic "onmessage" callback is invoked.


To know more of Async Servlets and XHR streaming check out Reverse Ajax and Async Servlets


Let's see a sample application involving SSE.


Note: All examples that follow are hosted on Github-SSE


We have a Weather data application that accepts a city and it's temperature.
All clients that are connected receive the changed data.

Version 1 - Simple app

The code is heavily commented.





Let's see different scenarios of execution

1. Time out is triggered from server side every 30 seconds

See this sample execution (note the time in chrome's developer console and logs given after)



Server log:
21:41:37.520 [http-nio-8080-exec-3]--SERVLET INITIALIZED--
21:41:37.522 [http-nio-8080-exec-3]--SSE REQUEST--
21:41:37.527 [http-nio-8080-exec-3]Event Registration for connection obj: org.apache.catalina.core.AsyncContextImpl@57a78329
21:41:54.193 [http-nio-8080-exec-4]--WEATHER POST DATA RECEIVED--
21:41:54.193 [http-nio-8080-exec-4]Current set of connections: [org.apache.catalina.core.AsyncContextImpl@57a78329]
21:41:54.194 [http-nio-8080-exec-4]Sending MSG to connection obj: org.apache.catalina.core.AsyncContextImpl@57a78329
21:42:07.578 [http-nio-8080-exec-5]--ASYNC EVENT TIMEOUT--
21:42:10.589 [http-nio-8080-exec-6]--SSE REQUEST--
21:42:10.589 [http-nio-8080-exec-6]Event Registration for connection obj: org.apache.catalina.core.AsyncContextImpl@599b6456
21:42:22.600 [http-nio-8080-exec-7]--WEATHER POST DATA RECEIVED--
21:42:22.600 [http-nio-8080-exec-7]Current set of connections: [org.apache.catalina.core.AsyncContextImpl@599b6456]
21:42:22.601 [http-nio-8080-exec-7]Sending MSG to connection obj: org.apache.catalina.core.AsyncContextImpl@599b6456
21:42:36.053 [http-nio-8080-exec-8]--WEATHER POST DATA RECEIVED--
21:42:36.053 [http-nio-8080-exec-8]Current set of connections: [org.apache.catalina.core.AsyncContextImpl@599b6456]
21:42:36.054 [http-nio-8080-exec-8]Sending MSG to connection obj: org.apache.catalina.core.AsyncContextImpl@599b6456
21:42:40.657 [http-nio-8080-exec-9]--ASYNC EVENT TIMEOUT--
21:42:43.666 [http-nio-8080-exec-10]--SSE REQUEST--
21:42:43.666 [http-nio-8080-exec-10]Event Registration for connection obj: org.apache.catalina.core.AsyncContextImpl@6ce8d8f8
21:43:13.725 [http-nio-8080-exec-1]--ASYNC EVENT TIMEOUT--
21:43:13.731 [http-nio-8080-exec-1]--ASYNC EVENT COMPLETE--

Console log:
startEventStream()
home.jsp:63 Event stream connection opened
home.jsp:72 100:Boston
Weather/v1:1 GET http://localhost:8080/ServerSentEvents/Weather/v1 net::ERR_INCOMPLETE_CHUNKED_ENCODING
home.jsp:67 Event stream connection error
home.jsp:63 Event stream connection opened
home.jsp:72 200:Boston
home.jsp:72 300:Boston
Weather/v1:1 GET http://localhost:8080/ServerSentEvents/Weather/v1 net::ERR_INCOMPLETE_CHUNKED_ENCODING
home.jsp:67 Event stream connection error
home.jsp:67 Event stream connection error

2. Client reconnects in case of disruption within 3 seconds

See this sample execution (note the time in chrome's developer console and logs)



21:47:57.233 [http-nio-8080-exec-9]--SSE REQUEST--
21:47:57.233 [http-nio-8080-exec-9]Event Registration for connection obj: org.apache.catalina.core.AsyncContextImpl@1c2ec20f
21:48:04.667 [http-nio-8080-exec-10]--WEATHER POST DATA RECEIVED--
21:48:04.668 [http-nio-8080-exec-10]Current set of connections: [org.apache.catalina.core.AsyncContextImpl@1c2ec20f]
21:48:04.668 [http-nio-8080-exec-10]Sending MSG to connection obj: org.apache.catalina.core.AsyncContextImpl@1c2ec20f
Aug 23, 2016 9:48:23 PM org.apache.catalina.core.StandardContext reload
INFO: Reloading Context with name [/ServerSentEvents] has started
21:48:23.280 [ContainerBackgroundProcessor[StandardEngine[Catalina]]]--SERVLET DESTROYED--
21:48:23.282 [http-nio-8080-exec-1]--ASYNC EVENT COMPLETE--
Aug 23, 2016 9:48:23 PM org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
WARNING: The web application [ServerSentEvents] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
com.mysql.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:43)
Aug 23, 2016 9:48:23 PM org.apache.jasper.servlet.TldScanner scanJars
INFO: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
Aug 23, 2016 9:48:23 PM org.apache.catalina.core.StandardContext reload
INFO: Reloading Context with name [/ServerSentEvents] is completed
21:48:26.308 [http-nio-8080-exec-10]--SERVLET INITIALIZED--
21:48:26.308 [http-nio-8080-exec-10]--SSE REQUEST--
21:48:26.310 [http-nio-8080-exec-10]Event Registration for connection obj: org.apache.catalina.core.AsyncContextImpl@6610b388
21:48:50.620 [http-nio-8080-exec-2]--WEATHER POST DATA RECEIVED--
21:48:50.621 [http-nio-8080-exec-2]Current set of connections: [org.apache.catalina.core.AsyncContextImpl@6610b388]
21:48:50.622 [http-nio-8080-exec-2]Sending MSG to connection obj: org.apache.catalina.core.AsyncContextImpl@6610b388


3. Multiple clients connecting to Server

See this sample execution



21:59:10.281 [http-nio-8080-exec-14]--SSE REQUEST--
21:59:10.281 [http-nio-8080-exec-14]Event Registration for connection obj: org.apache.catalina.core.AsyncContextImpl@2f05474f
21:59:13.623 [http-nio-8080-exec-15]--SSE REQUEST--
21:59:13.623 [http-nio-8080-exec-15]Event Registration for connection obj: org.apache.catalina.core.AsyncContextImpl@4b8a93fd
21:59:19.042 [http-nio-8080-exec-11]--WEATHER POST DATA RECEIVED--
21:59:19.042 [http-nio-8080-exec-11]Current set of connections: [org.apache.catalina.core.AsyncContextImpl@4b8a93fd, org.apache.catalina.core.AsyncContextImpl@2f05474f]
21:59:19.042 [http-nio-8080-exec-11]Sending MSG to connection obj: org.apache.catalina.core.AsyncContextImpl@4b8a93fd
21:59:19.042 [http-nio-8080-exec-11]Sending MSG to connection obj: org.apache.catalina.core.AsyncContextImpl@2f05474f
21:59:29.450 [http-nio-8080-exec-16]--WEATHER POST DATA RECEIVED--
21:59:29.450 [http-nio-8080-exec-16]Current set of connections: [org.apache.catalina.core.AsyncContextImpl@4b8a93fd, org.apache.catalina.core.AsyncContextImpl@2f05474f]
21:59:29.450 [http-nio-8080-exec-16]Sending MSG to connection obj: org.apache.catalina.core.AsyncContextImpl@4b8a93fd
21:59:29.451 [http-nio-8080-exec-16]Sending MSG to connection obj: org.apache.catalina.core.AsyncContextImpl@2f05474f
21:59:40.472 [http-nio-8080-exec-17]--ASYNC EVENT TIMEOUT--
21:59:43.698 [http-nio-8080-exec-20]--ASYNC EVENT TIMEOUT--
21:59:43.838 [http-nio-8080-exec-18]--SSE REQUEST--
21:59:43.839 [http-nio-8080-exec-18]Event Registration for connection obj: org.apache.catalina.core.AsyncContextImpl@7e0bc6a2
21:59:46.732 [http-nio-8080-exec-19]--SSE REQUEST--
21:59:46.732 [http-nio-8080-exec-19]Event Registration for connection obj: org.apache.catalina.core.AsyncContextImpl@5a895a77
22:00:13.922 [http-nio-8080-exec-12]--ASYNC EVENT TIMEOUT--
22:00:13.923 [http-nio-8080-exec-12]--ASYNC EVENT COMPLETE-- 
22:00:16.815 [http-nio-8080-exec-13]--ASYNC EVENT TIMEOUT--
22:00:16.817 [http-nio-8080-exec-13]--ASYNC EVENT COMPLETE-- 

You might have noticed is the net::ERR_INCOMPLETE_CHUNKED_ENCODING error in the chrome console when the server times out a Async context.

This can be ignored.

Looking at it, the way the default setup works is -

"Based on the connection time out value in this case 30 seconds, the server times out. This is the point when client decides to send a new request if the server has sent data/events at least once within the past 30 seconds, else, client closes the connection. Anytime when client faces a disconnection in the communication, it automatically tries to reconnect based on the retry value, the default being 3 seconds."


Things to ponder over.

>> When should you change the server side time out value? Should you change the default 30 seconds?
>> Client reconnects after 3 seconds? Is this necessary?

These values should be thoughtfully kept based on your scenario.

We will see how these can be modified, if a situation arises.

//changes retry value
response.getWriter().println("retry: 10000\n");
//changes asyncContext timeout value
ac.setTimeout(Integer.MAX_VALUE);

If you are running with this configuration, then you will notice that you would need a mechanism to manually close the connection either from client or server side.

To cancel a stream from the client, simply call: source.close();
To cancel a stream from the server, respond with a non "text/event-stream" Content-Type or return an HTTP status other than 200 OK (e.g. 404 Not Found).


Version 2 - Client side closing, Modified timeout and retry





Note that you will get a
java.lang.RuntimeException: Connection closed by client
if you try sending events after a connection is closed.

The tricky point here is since it's done manually, the server doesn't trigger a ASYNC EVENT TIMEOUT => the async object is still there in the Set of asyncContexts.

So in most cases it's better to keep a value between 30 to 50 sec as async time out value and an intelligent algorithm to manually change the retry value based on the time of day/week.


Version 3 - Using event ids to automatically track

Remember that I mentioned about event id tracking feature of SSE.

The server is allowed to associate an arbitrary ID string with each message. The browser automatically remembers the last seen ID and will automatically append a "Last-Event-ID" HTTP header with the remembered value when issuing a reconnect request.




Version 4 - Using event ids to keep track of last 5 events alone

You need a manual tracking of messages/events if you need to do any of this
- send last 5 messages to a newly connected client
- send a max of last 5 messages missed in case of reconnection. (Note that the default feature sends the missed out events but this is a case when we are in need to modify the default setting - I haven't tried this)








1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete