Search This Blog

Tuesday 29 January 2019

Server-Sent Events - How to do it in Sevlets ?

In the past few posts I have seen some techniques to pull of Push Notifications. HTML5 has introduced a couple of new methods - one of them is Server -Sent Events. In this post I am trying to build one such Servlet based implementation.
One of the prime points to note is that I do not need Servlet 3.x to pull this off. I can do with old style Servlet. What I do need is an HTML5 compatible web browser.
I decided to build a servlet that tells me the mood of a famous personality at any given time. The code is nothing but overuse of Randomness to pull of the statements:
public class MoodServlet extends HttpServlet {

  private final List<String> celebrities;
  private final List<String> activities;
  private final List<String> cities;
  private final List<String> companions;

  {
    celebrities = Arrays.asList("Amitabh Bachchan", "Tom Cruise", "Steve Buscemi",
        "Madhuri Dixit", "Mary Kom", "Sachin Tendulkar");
    activities = Arrays.asList("reading News", "watching Tennis", "out for a walk",
        "at a bar", "working ", " taking a pleasant nap");
    cities = Arrays.asList("Mumbai", "Pune", "Goa", "Kerala", "Jammu", "Ladakh");
    companions = Arrays.asList("Family", "Friends", "dog", "Colleages", "kids", "spouse",
        "neighbors");
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    int waitTimeBetweenPosts = (int) (Math.random() * 5);
    String statement1 = getCelebrityMood();
    String statement2 = getCelebrityMood();
    // content type must be set to text/event-stream
    resp.setContentType("text/event-stream");

    // encoding must be set to UTF-8
    resp.setCharacterEncoding("UTF-8");
    PrintWriter writer = resp.getWriter();
    writer.write("data: wait time - " + waitTimeBetweenPosts + " seconds " + "\n");
    writer.write("data: " + statement1+ "\n");
    try {
      Thread.sleep(waitTimeBetweenPosts  * 1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    writer.write("data: " + statement2+ "\n");
    writer.write("\n");
    writer.flush();
    writer.close();
  }

  private String getCelebrityMood() {
    return celebrities.get((int) (Math.random() * celebrities.size()))
        + " : I wish I was " + activities.get((int) (Math.random() * activities.size()))
        + " at " + cities.get((int) (Math.random() * cities.size())) + " with my "
        + companions.get((int) (Math.random() * companions.size()));
  }

}
The interesting parts in the code is :
  1. The class is a normal HttpServlet. 
  2. The content type however distinguishes this as server side events - "text/event-stream"
  3. Each write to the stream is prefixed with "data:" and ended with as "\n"
  4. The end of message is indicated by a "\n". 
So a sample message would be of the form
data: wait time - 2 seconds 
\ndata: Tom Cruise : I wish I was reading News at Goa with my spouse\n
data: Madhuri Dixit : I wish I was watching Tennis at Goa with my spouse\n\n
This whole is one single message. This message will now be processed at our client:
<!DOCTYPE HTML>
<html>
<body>
    <b> What's trending on Page 3 </b>:
     <div id="messages"></div>
    <br/><br/>
    
    <button onclick="start()">GetMessaged</button>
 
    <script type="text/javascript">

         function start() {
            if (!!window.EventSource) {
               var eventSource = new EventSource("mood");

            } else {
               alert("Your client does not suport server sent events !")
            }

            eventSource.onmessage = function(event) {
               var value = document.getElementById('messages').innerHTML;
               document.getElementById('messages').innerHTML = value
                     + "<br/>" + event.data;
            };
         }
      </script>
</body>
</html>
The above message as interpreted by the browser would be:
wait time - 2 seconds Tom Cruise : I wish I was reading News at Goa with my spouse Madhuri Dixit : I wish I was watching Tennis at Goa with my spouse
The page looks as follows:
If we look at Firebug, we can see that the browser sent requests at regular intervals to this page:
 The cool thing about server sent events is that we can associate an event with our data. I made a small change to the servlet code:
   String[] events = new String[] {"red","blue","green"};
    writer.write("event: " + events[(int) (Math.random()*events.length)]  +"\n");
    writer.write("data: wait time - " + waitTimeBetweenPosts + " seconds " + "\n");
    writer.write("data: " + statement1 + "\n");
    pause(waitTimeBetweenPosts);
    //Failed to create two events in a single message
//    writer.write("event: " + events[(int) (Math.random()*events.length)]  +"\n");
    writer.write("data: " + statement2 + "\n");
    writer.write("\n");
We have introduced three new events - "red","green","blue". Here we preceded our message with information about the event type. Along the lines of
event: X \n
data: Y \n
data: Z \n\n
Then some change in the browser to listen for different events:
function start() {
    if (!!window.EventSource) {
       var eventSource = new EventSource("mood");

    } else {
       alert("Your client does not suport server sent events !")
    }
    
    
    eventSource.addEventListener('green', function(event) {
        var value = document.getElementById('messages').innerHTML;
        document.getElementById('messages').innerHTML = value
        + "<br/> <font color='green'>" +event.data+"</font>";
         
    }, false);
    eventSource.addEventListener('red', function(event) {  
        var value = document.getElementById('messages').innerHTML;
        document.getElementById('messages').innerHTML = value
        + "<br/> <font color='red'>" +event.data+"</font>";
         
    }, false);
    eventSource.addEventListener('blue', function(event) {
        var value = document.getElementById('messages').innerHTML;
        document.getElementById('messages').innerHTML = value
        + "<br/> <font color='blue'>" +event.data+"</font>";
         
    }, false);
    eventSource.onerror( function(event) {
       alert("Failed to execute the event");
         
    }, false);
    
 }
The output looks like below:
 I tried to included multiple events in a single message but that did not work.
Also there is an onError event available in java script to process any failures in the code.

No comments:

Post a Comment