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

Reverse AJAX - Old school

Reverse Ajax is essentially a concept: being able to send data from the server to the client.

There are various ways to achieve this

1. HTTP polling

Polling involves issuing a request from the client to the server to ask for some data.
This is obviously a mere Ajax HTTP request. To get the server events as soon as possible, the polling interval (time between requests) must be as low as possible.
There's a drawback: if this interval is reduced, the client browser is going to issue many more requests, many of which won't return any useful data, and will consume bandwidth and processing resources for nothing.


Advantages: It's really easy to implement and does not require any special features on the server side. It also works in all browsers.
Disadvantage: This method is rarely employed because it does not scale at all.

@WebServlet("/events")
public final class PollingServlet extends HttpServlet {
 
 private final Random random = new Random();
 private final BlockingQueue messages = new LinkedBlockingQueue();
 private final Thread generator = new Thread("Event generator") {
  @Override
  public void run() {
   println("Listening thread started");
   while (!Thread.currentThread().isInterrupted()) {
    try {
     Thread.sleep(random.nextInt(8000));
    } catch (InterruptedException e) {
     Thread.currentThread().interrupt();
    }
    println("Inserting 1 msg into queue");
    messages.offer("At " + new Date());
   }
  }
 };

 @Override
 public void init() throws ServletException {
  generator.start();
 }

 @Override
 public void destroy() {
  generator.interrupt();
 }

 @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  List messages = new LinkedList();
  println("Removing "+ this.messages.size() +" msgs from queue");
  this.messages.drainTo(messages);
  resp.setStatus(HttpServletResponse.SC_OK);
  resp.setContentType("application/json");
  resp.getWriter().write(new JSONArray(messages).toString());
  resp.getWriter().flush();
 }
 
 public static void println(String output) {
  System.out.println("[" + Thread.currentThread().getName() + "]" + output);
 }
}


<html>
<head>
    <title>HTTP Polling</title>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
    <script type="text/javascript" src="http://jquery-json.googlecode.com/files/jquery.json-2.2.min.js"></script>
    <script type="text/javascript">
        jQuery(function($) {
            setInterval(function() {
                $('body').append('<span>[client] checking for events...</span><br/>');
                $.getJSON('events', function(events) {
                    if(events.length) {
                        $('body').append('<span style="color: blue;">[client] ' + events.length + ' events</span><br/>');
                    } else {
                        $('body').append('<span style="color: red;">[client] no event</span><br/>');
                    }
                    for (var i in events) {
                        $('body').append('<span>[event] ' + events[i] + '</span><br/>');
                    }
                });
            }, 2000);
        });
    </script>
</head>
<body style="font-family: monospace;">
</body>
</html>

Server logs:
[Event generator]Listening thread started
[http-nio-8080-exec-2]Removing 0 msgs from queue
[http-nio-8080-exec-3]Removing 0 msgs from queue
[Event generator]Inserting 1 msg into queue
[http-nio-8080-exec-4]Removing 1 msgs from queue
[http-nio-8080-exec-5]Removing 0 msgs from queue
[Event generator]Inserting 1 msg into queue
[http-nio-8080-exec-6]Removing 1 msgs from queue
[http-nio-8080-exec-7]Removing 0 msgs from queue
[Event generator]Inserting 1 msg into queue
[http-nio-8080-exec-8]Removing 1 msgs from queue
[Event generator]Inserting 1 msg into queue
[http-nio-8080-exec-9]Removing 1 msgs from queue
[Event generator]Inserting 1 msg into queue
[http-nio-8080-exec-10]Removing 1 msgs from queue
[http-nio-8080-exec-1]Removing 0 msgs from queue
[Event generator]Inserting 1 msg into queue
[http-nio-8080-exec-2]Removing 1 msgs from queue
[Event generator]Inserting 1 msg into queue
[http-nio-8080-exec-3]Removing 1 msgs from queue
[http-nio-8080-exec-4]Removing 0 msgs from queue
[Event generator]Inserting 1 msg into queue
[Event generator]Inserting 1 msg into queue

[client] checking for events...
[client] no event
[client] checking for events...
[client] no event
[client] checking for events...
[client] 1 events
[event] At Wed Aug 26 08:51:42 IST 2015
[client] checking for events...
[client] no event
[client] checking for events...
[client] 1 events
[event] At Wed Aug 26 08:51:46 IST 2015
[client] checking for events...
[client] no event
[client] checking for events...
[client] 1 events
[event] At Wed Aug 26 08:51:50 IST 2015

There are many things to note here.
- Since tomcat 8 uses NIO connector by default it uses threads from a pool and that's evident when you notice the thread names for different requests
- Note that in the example above client keeps polling infinitely and the server keeps queuing infinitely. Both shouldn't happen and should be handled in real
scenarios.

2. Piggyback

Piggyback polling is a much more clever method than polling since it tends to remove all non-needed requests (those returning no data).
There is no interval; requests are sent when the client needs to send a request to the server. The difference lies in the response, which is split into two parts: the response for the requested data and the server events if any occurred.


Advantages: With no requests returning no data since the client controls when it sends requests, you have less resource consumption. It also works in all browsers and does not require special features on the server side.
Disadvantage: You have no clue when the events accumulated on the server side will be delivered to the client because it requires a client action to request them.


@WebServlet("/events")
public final class PiggybackServlet extends HttpServlet {

 private final Random random = new Random();
 private final BlockingQueue messages = new LinkedBlockingQueue();
 private final Thread generator = new Thread("Event generator") {
  @Override
  public void run() {
   println("Listening thread started");
   while (!Thread.currentThread().isInterrupted()) {
    try {
     Thread.sleep(random.nextInt(8000));
    } catch (InterruptedException e) {
     Thread.currentThread().interrupt();
    }
    println("Inserting 1 msg into queue");
    messages.offer("At " + new Date());
   }
  }
 };

 @Override
 public void init() throws ServletException {
  generator.start();
 }

 @Override
 public void destroy() {
  generator.interrupt();
 }

 @Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  System.out.println("FORM POSTED !");
  List messages = new LinkedList();
  this.messages.drainTo(messages);
  resp.setStatus(HttpServletResponse.SC_OK);
  resp.setContentType("application/json");
  try {
   resp.getWriter().write(new JSONObject().put("events", new JSONArray(messages)).put("formValid", true).toString());
  } catch (JSONException e) {
   throw new ServletException(e.getMessage(), e);
  }
  resp.getWriter().flush();
 }

 @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  List messages = new LinkedList();
  println("Removing "+ this.messages.size() +" msgs from queue");
  this.messages.drainTo(messages);
  resp.setStatus(HttpServletResponse.SC_OK);
  resp.setContentType("application/json");
  resp.getWriter().write(new JSONArray(messages).toString());
  resp.getWriter().flush();
 }
 
 public static void println(String output) {
  System.out.println("[" + Thread.currentThread().getName() + "]" + output);
 }
}


<html>
<head>
    <title>HTTP Piggy</title>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
    <script type="text/javascript" src="http://jquery-json.googlecode.com/files/jquery.json-2.2.min.js"></script>
    <script type="text/javascript">
        jQuery(function($) {

            function processEvents(events) {
                if (events.length) {
                    $('#logs').append('<span style="color: blue;">[client] ' + events.length + ' events</span><br/>');
                } else {
                    $('#logs').append('<span style="color: red;">[client] no event</span><br/>');
                }
                for (var i in events) {
                    $('#logs').append('<span>[event] ' + events[i] + '</span><br/>');
                }
            }

            $('#submit').click(function() {
                $('#logs').append('<span>[client] checking for events...</span><br/>');
                $.post('events', function(data) {
                    $('#logs').append('<span>[server] form valid ? ' + data.formValid + ' </span><br/>');
                    processEvents(data.events);
                });
            });

        });
    </script>
</head>
<body>
 <button id="submit">Submit form</button>
 <div id="logs" style="font-family: monospace;"></div>
</body>
</html>


Server logs:
[Event generator]Listening thread started
FORM POSTED !
[Event generator]Inserting 1 msg into queue
FORM POSTED !
[Event generator]Inserting 1 msg into queue
[Event generator]Inserting 1 msg into queue
FORM POSTED !


[client] checking for events...
[server] form valid ? true
[client] no event
[client] checking for events...
[server] form valid ? true
[client] 1 events
[event] At Wed Aug 26 09:18:45 IST 2015
[client] checking for events...
[server] form valid ? true
[client] 2 events
[event] At Wed Aug 26 09:18:50 IST 2015
[event] At Wed Aug 26 09:18:53 IST 2015


Note that I clicked the button 3 times.



No comments:

Post a Comment