Search This Blog

Sunday 27 March 2016

Reverse AJAX - Using the Piggback Technique

In the previous post we saw how reverse AJAX was achieved using the polling technique. Although easy, this is not a very popular or scalable solution. An alternative approach is the Piggback Technique.
In this there is no continuous requests being sent from client to check if any event has occurred. There is no interval here. The client functions as normal. Whenever it needs any specific data, it sends a request to the server. The server processes the client requests and returns the response. But - and this where things get interesting - along with the response data, the server adds any additional information that it wants to provide to the client.
Thus Server is piggybacking its information on the clients request for some other data.
Consider that the client has demanded to be made aware of certain events - e.g. A Meeting Join Invite. In the polling mode, the client would keep pinging the server every x minutes/seconds to check if there are any invites. In this case, whenever a client request for any data arrives, the server will also include any invites if present.
Consider the same index.jsp application from last post.
<html>
<head>
<script>

 var pollAndCheck = function() {
  document.getElementById("sessionActve").innerHTML = "Checking...";
  if (window.XMLHttpRequest) {
   // code for IE7+, Firefox, Chrome, Opera, Safari
   xmlhttp = new XMLHttpRequest();
  } else { // code for IE6, IE5
   xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
  }
  xmlhttp.onreadystatechange = function() {
   if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {

    var xmlDoc = xmlhttp.responseXML;
    var value = xmlDoc.getElementsByTagName("value")[0].childNodes[0].nodeValue;
    document.getElementById("sessionActve").innerHTML = value;
    processForEvents(xmlDoc);
   }
  }
  xmlhttp.open("POST", "ajax/isActive", true);
  xmlhttp.send();
 };

 var processForEvents = function(xmlDoc) {
  var eventCount = xmlDoc.getElementsByTagName("eventC")[0].childNodes[0].nodeValue
  if (eventCount > 0) {
   document.getElementById("eventHighlighter").innerHTML = "<b>"
     + eventCount
     + " Event Invites received ! Goto Events page o review. </b>";
  }
 };

 //check every 1 minute
 window.setTimeout(pollAndCheck, (1 * 60 * 1000));
</script>

</head>



<%
  session = request.getSession(false);
  if (session != null) {
    session.invalidate();
  }
  session = request.getSession(true);
  System.out.println("Session has been activated at " + session.getCreationTime());
%>

<body>
     <h3>Hello, This is a sample page</h3>
      <br />
      <br /> Is your session active ??
      <div id="sessionActve"></div>
      <div id="eventHighlighter"></div>
</body>
</html>

On the server side, I had to introduce some technique to intercept the code and The whole process worked as below:
  1. I decided to intercept all AJAX requests processed by the server.
  2. Before the response is written back to the client, the code for piggybacking any server data is executed and the result added to the response.
I wouldn't say mine is a very clean technique, rather it is just an approach to demonstrate piggybacking data on server end.
public class FinalResponseGeneratingFilter implements Filter {

  @Override
  public void destroy() {
  }

  @Override
  public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain filterChain)
      throws IOException, ServletException {
    HttpServletResponse response = (HttpServletResponse) arg1;
    HttpServletRequest request = (HttpServletRequest) arg0;
    HttpServletResponseWrapper responseWrapper = new DummyResponseWrapper(response);
    filterChain.doFilter(request, responseWrapper);
    
    int events = EventUtils.getEvents();//COMPLEX logic to collect events
    String data = "<data><value>"
        + StringEscapeUtils.escapeXml(responseWrapper.toString()) + "</value>"
        + "<eventC>" + events + "</eventC>" + "</data>";
    response.getWriter().write(data);
    response.getWriter().flush();

  }

  @Override
  public void init(FilterConfig arg0) throws ServletException {
  }

}
As seen above the Filter uses a ResponseWrapper to collect all the response. It then executes some server logic to collect information on events. This is then written back to the actual stream.
The ResponseWrapper implementation is as below:
public class DummyResponseWrapper extends HttpServletResponseWrapper {
  protected StringWriter stringWriter;
  protected PrintWriter writer;
  protected boolean getOutputStreamCalled;
  protected boolean getWriterCalled;

  public DummyResponseWrapper(HttpServletResponse response) {
    super(response);
    stringWriter = new StringWriter();
  }

  public ServletOutputStream getOutputStream() throws IOException {
    if (getWriterCalled) {
      throw new IllegalStateException("getWriter already called");
    }
    getOutputStreamCalled = true;
    return super.getOutputStream();
  }

  public PrintWriter getWriter() throws IOException {
    if (writer != null) {
      return writer;
    }
    if (getOutputStreamCalled) {
      throw new IllegalStateException("getOutputStream already called");
    }
    getWriterCalled = true;
    writer = new PrintWriter(stringWriter);
    return writer;
  }

  public String toString() {
    String streamContent = null;

    if (writer != null) {
      streamContent = stringWriter.toString();
    }
    return streamContent;
  }
}
The class collects all the data in a StringWriter.Lastly had to configure my web.xml to have the intercept happen.
<servlet>
      <servlet-name>SessionCheckServlet</servlet-name>
      <servlet-class>com.app.web.SessionCheckServlet</servlet-class>
   </servlet>
   <servlet-mapping>
      <servlet-name>SessionCheckServlet</servlet-name>
      <url-pattern>/ajax/isActive</url-pattern>
   </servlet-mapping>
   
   <filter>
        <filter-name>FinalResponseGeneratingFilter</filter-name>
        <filter-class>com.app.filter.FinalResponseGeneratingFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>FinalResponseGeneratingFilter</filter-name>
        <url-pattern>/ajax/*</url-pattern>
    </filter-mapping>

This is how the output would be:
The advantage of this technique is less resource consumption. With no polling requests (thus eliminating the requests that return no data) there is less resource consumption at server. The technique is pure java script that works across browsers and does not require special features on the server side. The disadvantage in this technique is its timeliness. The events accumulated on the server side will be delivered to the client at a time client action is received. It is not immediate.

1 comment:


  1. All are saying the same thing repeatedly, but in your blog I had a chance to get some useful and unique information, I love your writing style very much, I would like to suggest your blog in my dude circle, so keep on updates.


    Digital Marketing Company in Chennai

    ReplyDelete