3

I want to implement long polling in a java web application. Basically, when a user logs in, I want him to be hooked into a notification service. I want to push out new notifications to him from the server as they occur, and I want him to see those notifications in real time. ( so short polling or periodically checking with server from the client aren't good enough).

How can I do this? Essentially, I want a way to push a string message from the server, and for the client to receive it immediately.

I've heard some references that this could be done using a 'http chunk transfer' header from the server. But how would that be set up on the client?

Ali
  • 261,656
  • 265
  • 575
  • 769

2 Answers2

6

Although I'm answering late, I'll go ahead and include my answer. If you are using HTML 5, try using HTML 5 - Server Sent Events (SSE) along with HTML 5 - Web Workers. Note that SSE is not supported by MS IE as of now.

On Struts2 end, there could be a standard action to handle the request.

Also see this question. You can also see the current compatibility here. There is a demo here, where we can see the periodic requests being made (in network monitor) and gives a good idea of what can be done.

Also note that the URL (event source) can only be assigned once.

Update : From what I have looked around additionally (see here and here), SSE actually maintains a connection so as to receive periodic updates from the server. As such the server can also have an 'infinite loop' that goes on until the client terminates and then tries to reconnect in which case the server can again resend events.

Determining which events were missed by the client should be handled by the implementation if or as necessary.

Here is a link that demos SSE with a connection being maintained. As per the spec we should also be able to send a 'Last-Event-ID' in the header. However yet to find an example that uses it!

Update 2 : An example that maintains connection and returns response using HttpServletResponse and another that repeatedly polls action and returns response using s2 stream result. Regarding frequency of polling when no connection is maintained, chrome appears to be 3 seconds where as firefox's is much larger.

1.) SSE.java

public class SSE extends ActionSupport {

    public String handleSSE() {

        HttpServletResponse response = ServletActionContext.getResponse();

        response.setContentType("text/event-stream");
        response.setCharacterEncoding("UTF-8");

        System.out.println("Inside handleSSE()+suscribe "+Thread.currentThread().getName());

        int timeout = 15*1000;
        long start = System.currentTimeMillis();
        long end = System.currentTimeMillis();

        while((end - start) < timeout) {

            try {
                PrintWriter printWriter = response.getWriter();
                printWriter.println(  "data: "+new Date().toString() );
                printWriter.println(); // note the additional line being written to the stream..
                printWriter.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            end = System.currentTimeMillis();

        }

        System.out.println("Exiting handleSSE()-suscribe"+Thread.currentThread().getName());

        return SUCCESS;
    }
}

2.) SSES2.java

public class SSES2 extends ActionSupport {


    private InputStream sseStream;

    public InputStream getSseStream() {
        return sseStream;
    }

    public String handleSSES2() {
        System.out.println("Inside handleSSES2() ");
        String result = "data: "+new Date().toString() + "\n\n";
        sseStream = new ByteArrayInputStream(result.getBytes() );
        System.out.println("Exiting handleSSES2() ");
        return SUCCESS;
    }
}

3.) struts.xml

<struts>
    <include file="strutsBase.xml"/>
</struts>

4.) strutsBase.xml

<struts>

    <!-- Configuration for the default package. -->
    <package name="strutsBase" extends="struts-default" >


        <!-- Default interceptor stack. -->
        <default-interceptor-ref name="basicStack"/>

        <action name="suscribe" class="com.example.struts2.sse.action.SSE" method="handleSSE">
            <result name="success">/view/empty.txt</result>
            <!-- we don't need the full stack here -->
        </action>

        <action name="suscribeStateless" class="com.example.struts2.sse.action.SSES2" method="handleSSES2">
            <result name="success" type="stream">
                <param name="contentType">text/event-stream</param>
                <param name="inputName">sseStream</param>
            </result>
        </action>


    </package>
</struts>

sse.html

<!doctype html>
<meta charset="utf-8">
<title>EventSource demo</title>
<h1>new EventSource() for S2</h1>
<p><output id="result">OUTPUT VALUE</output></p>
<script>
(function(global, window, document) {
  'use strict';

  function main() {
    window.addEventListener('DOMContentLoaded', contentLoaded);
  }

  function contentLoaded() {
    var result = document.getElementById('result');
    var stream = new EventSource('suscribe.action');
    stream.addEventListener('message', function(event) {
      var data = event.data;
      result.value = data;
    });
  }

  main();
})(this, window, window.document);
</script>
<!-- 
Also See :
http://server-sent-events-demo.herokuapp.com/
 -->

sseAction.html

Same as sse.html except that EventSource url is 'suscribeStateless.action' instead of suscribe.action
Community
  • 1
  • 1
Ravindra HV
  • 2,558
  • 1
  • 17
  • 26
  • That explains the client side of it, but on the server-side, e.g with Struts, how would I handle periodically sending data? Would I just do `request.write()`? – Ali Nov 10 '13 at 19:26
  • @ClickUpvote For sending a periodic response while maintaining a connection, that would do it, yes. You may have to call flush once you are done writing for a given event. – Ravindra HV Nov 11 '13 at 16:17
  • Can you share some code on how to do that, plus the flushing? I can accept after you do add that. Thanks. – Ali Nov 11 '13 at 19:00
1

I would recommend using an existing library like Atmosphere if you can. Atmosphere automatically tries something more modern but falls back to long polling if needed. Long polling can be a bit tricky to get working manually. There are undocumented things, such as some proxies or load balancers requiring certain configuration settings or padding to work with long polling.

Robin Green
  • 32,079
  • 16
  • 104
  • 187