users@grizzly.java.net

Re: Problem with comet-based web app -- Streaming

From: Kawajiri Takeshi <taken.kz_at_gmail.com>
Date: Tue, 4 Nov 2008 13:42:31 +0900

hello.

I've wrote streaming client by Prototype, like below.
This approach need different response-format per browser...but it works fine :)
(and it don't spin browser-loading-icon)

I hope you find it informative.
thanks.

# for IE 6,7
------------------Server----------------------------
Preparation: Send BODY element. like this.
---
public class YourServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        //TODO: attach your handler.
        res.setCharacterEncoding("UTF-8");
        res.setContentType("text/html; charset=UTF-8");
        res.setHeader("Cache-Control", "private");
        res.setHeader("Pragma", "no-cache");
        res.setHeader("Transfer-Encoding", "Chunked");
        PrintWriter writer = res.getWriter();
        writer.write("<html><head><meta http-equiv=\"Content-Type\"
content=\"text/html; charset=UTF-8\" /></head><body
onload=\"parent.callback('reconnect', false)\">";);
        writer.flush();
    }
}
---
Data push: Send your data with SCRIPT element. like this.
---
public class YourHandler implements CometHandler<BrowserConnection> {
    public final static String SCRIPT_TEMP = "<script
type=\"text/javascript\">if(parent.callback)parent.callback(\"%s\",%s);</script>";
    private void onNotify(CometEvent event) throws IOException {
        //TODO: get your data from event
        writer.write(String.format(SCRIPT_TEMP, "your_event_name",
"your_data"));
        writer.flush();
    }
}
------------------client----------------------------
var IeStream = Class.create();
Object.extend(IeStream.prototype, {
  initialize: function(url, callback) {
    this.url = url;
    this.xdoc = new ActiveXObject("htmlfile");
    this.xdoc.open();
    this.xdoc.write("<html><body></body></html>");
    this.xdoc.close();
    this.callback = callback;
    this.xdoc.parentWindow.callback = this.callback;
    Event.observe(window, 'unload', function() {
      this.callback = undefined;
      this.xdoc = null;
      CollectGarbage();
    }.bind(this), false);
  },
  connect: function() {
    this.xdoc.body.innerHTML = '<iframe src="' + this.url + '"></iframe>';
  }
});
# for Safari, FF, CHROME
------------------Server----------------------------
Preparation: Send HTML header. like this.
---
public class YourServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        //TODO: attach your handler.
        res.setCharacterEncoding("UTF-8");
        res.setContentType("text/html; charset=UTF-8");
        res.setHeader("Cache-Control", "private");
        res.setHeader("Pragma", "no-cache");
        res.setHeader("Transfer-Encoding", "Chunked");
        PrintWriter writer = res.getWriter();
        writer.write("<meta http-equiv=\"Content-Type\"
content=\"text/html; charset=%s\" />";);
        writer.flush();
    }
}
---
Data push: Send your data with SCRIPT element. like this.
---
public class YourHandler implements CometHandler<BrowserConnection> {
    public final static String SCRIPT_TEMP = "<script
type=\"text/javascript\">if(parent.callback)parent.callback(\"%s\",%s);</script>";
    private void onNotify(CometEvent event) throws IOException {
        //TODO: get your data from event
        writer.write(String.format(SCRIPT_TEMP, "your_event_name",
"your_data"));
        writer.flush();
    }
}
------------------client----------------------------
var InteractiveXhrStream = Class.create();
Object.extend(InteractiveXhrStream.prototype, {
  FUNCTION_START: '<script
type="text/javascript">if(parent.callback)parent.callback(',
  FUNCTION_END: ');</' + 'script>',
  initialize: function(url, callback) {
    this.url = url;
    this.callback = callback;
  },
  connect: function() {
    var offset = 0;
    new Ajax.Request(this.url, {
      method: 'get',
      scope: this,
      contentType: 'text/plane;charset=UTF-8',
      encoding: 'UTF-8',
      onInteractive: function(stream) {
        var buffer = stream.responseText;
        var newdata = buffer.substring(offset);
        offset = buffer.length;
        while(1) {
          var x = newdata.indexOf(this.FUNCTION_START);
          if(x != -1) {
            var y = newdata.indexOf(this.FUNCTION_END,x);
            if(y != -1) {
              var parameters = newdata.substring(x +
this.FUNCTION_START.length, y);
              try {
                var command = eval(parameters.substring(0,
parameters.indexOf(",")));
                var data = eval( '(' +
parameters.substring(parameters.indexOf(",") + 1, parameters.length) +
')');
                this.callback(command,data);
              } catch(e) {
              }
              newdata = newdata.substring(y + this.FUNCTION_END.length);
            } else {
              break;
            }
          } else {
            break;
          }
        }
        offset = buffer.length - newdata.length;
      }.bind(this),
      onSuccess: function(stream) {
        this.callback("reconnect", false);
      }.bind(this)
    });
  }
});
# for Opera
------------------Server----------------------------
Preparation: Send x-dom-event-stream header. like this.
---
public class YourServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        //TODO: attach your handler.
        res.setCharacterEncoding("UTF-8");
        res.setContentType("application/x-dom-event-stream; charset=UTF-8");
        res.setHeader("Cache-Control", "private");
        res.setHeader("Pragma", "no-cache");
        res.setHeader("Transfer-Encoding", "Chunked");
        PrintWriter writer = res.getWriter();
        writer.write("<html><head><meta http-equiv=\"Content-Type\"
content=\"text/html; charset=UTF-8\" /></head><body
onload=\"parent.callback('reconnect', false)\">";);
        writer.flush();
    }
}
---
Data push: Send your data with Event-Source. like this.
---
public class YourHandler implements CometHandler<BrowserConnection> {
    public final static String EVENT_TEMP = "Event: %s\ndata: %s\n\n";
    private void onNotify(CometEvent event) throws IOException {
        //TODO: get your data from event
        writer.write(String.format(EVENT_TEMP, "your_event_name", "your_data"));
        writer.flush();
    }
}
------------------client----------------------------
var EventStream = Class.create();
Object.extend(EventStream.prototype, {
  initialize: function(url, callback) {
    this.url = url;
    this.callback = callback;
  },
  connect: function() {
    var source = new Element('event-source');
    source.addEventListener('your_event_name', function(e) {
      var data = eval('(' + e.data + ')');
      this.callback("your_event_name", data);
    }.bind(this), false);
    source.setAttribute('src', this.url);
    if (opera.version() < 9.5) {
      document.body.appendChild(source);
    }
  }
});
--------usage---------------
var your_stream_class = getStream();
var stream = new your_stream_class("/your_path", function(event_name, data) {
  //:TODO update your html-element!
});
stream.connect();
> Ohhh man, your lucky , I've just spent about a week dealing with some of the streaming issues with Client side JS :). I hope I can save you some time and a few mild strokes :). I'm gonna forward this to the mailing list so I can get some input, maybe even start a muse or someone that's been there can enlighten me.. Below is what I found and the conclusion that I've come to. The problems with streaming lie in the twisted heap of wires know as Cross Browser JS.
>
> 1. if your using FF and you try to modify the document object while a stream is open it will terminate the connection. Don't use document.write() .. this took me about 1 full day to figure out and it was REALLY frustrating (All JS is frustrating as debuging is so cryptic). I believe this is a bug in FF. I have found a few in IE as well.
>
> 2. I use Prototype, as it simply rocks :). seriously though, its indispensable for coding larger client side segments in JS. I don't use Jmaki, but I think that's where your problems are.. I'm sure it has a wrapper for the XMLHttpRequest object and it was not intended to handle streaming. actually XMLHttpRequest is intended to handle streaming but MS imlementations of it fail to do so. Anyway about prototype, I had to modify it a bit to deal with the streaming comet, and really at the end of the day you might as well write your own implementation of XMLHttpRequest as the onInteractive ( if(readyState==3) )  method of the Ajax "Class" has NO abort(); method...
>
> 3. Streaming is troublesome, since IE wont let you read the object until readyState=4 , so essentialy, what you have to do is...  either use some iframe voodoo ( which shows the spinner, i personaly feel thats unacceptable) or use a technique that, does a dissconnect/reconnect after every push. in your onEvent(CometEvent e)... add this after the flush(), <CometContext>.removeCometHandler(this); . Then, in your client specify a reconnect after every response is recived you will reconnect and wait for the next push... There are other solutions, and Im exploring them as I type this.. such as using a flash applet or Java applet for client/server communications.
>
> The Flash Applet method has problems (flaky actually with v9) and the Java applet method works great but would require more users to download a plugin... So im working on a mesh of systems that will detect, install and try the various "Drivers" ( my silly term :) ) for the clients. ie6 is still 24% of the market so it has to be addressed. anyway, I look forward to uploading my work in this area shortly.
>
> P.S.
> Im having little luck with any JS methods + IE6 regaurdless but i'm under Linux / Wine , so .. who knows whats going on :) If you come up with any cool ideas feel free to share.
>
> --Richard
>
>
>
>
>
>
> ________________________________
> From: Hiram Coffy <hiram.coffy_at_pb.com>
> To: Richard Corbin <igf1_at_yahoo.com>
> Sent: Friday, October 31, 2008 7:13:47 PM
> Subject: RE: Problem with comet-based web app **SOLVED**
>
>
> Hello Richard,
>
> Remember me, I壇 like to peak your brain for a moment. I am seeing a set of peculiar behaviors with my simple comet web app. I am using a jMaki client that makes a async call to my comet servlet. 1) I noticed that unless I call close on the connection the data does not get flushed out to the client. Calling flush appears not to have the desired effect. 2) It appears that the connection cannot be held open regardless of what the server does.
>
> I don稚 know if you have any experience with jMaki. Any help would be much appreciated.
>
> Noticed that my code is setup according to Sun痴 official tutorial. But I believe in the end the wiring is similar to the approach you recommended.
>
> Relevant bits of code:
>
> Client:
> jmaki.subscribe("/bn/refresh/*", function(args) {
>
>     jmaki.doAjax({method: "GET",
>         url: "RefreshServlet",
>         callback: function(_req) {
>             var tmp = _req.responseText;
>             var obj = eval("(" + tmp + ")");
>
>             jmaki.publish('/bn/setValues', obj);
>             document.getElementById('counter').innerHTML ="<div id='counter'><b>" + new Date().toString() + "</b></div>";
>         }
>     });
> });
>
> Server:
>     @Override
>     public void init(ServletConfig config) throws ServletException {
> ・
>
>         /* Configure this web app server to work in async mode using comet engine */
>         ServletContext ctx = config.getServletContext();
>         contextPath = ctx.getContextPath();
>
>         CometEngine engine = CometEngine.getEngine();
>         CometContext cctx = engine.register(contextPath);
>
>         //cctx.setExpirationDelay(30 * 1000);
>         cctx.setExpirationDelay(-1);  //Doesn稚 seem to make any difference
> ..
>     }
>
>     @Override
>     protected void doGet(HttpServletRequest request, HttpServletResponse response)
>         throws ServletException, IOException {
>
>         registerHandler(response, request);
>         processRequest(request, response);
>     }
>
>     private void registerHandler(HttpServletResponse response, HttpServletRequest request)
>         throws IllegalStateException {
>         HttpSession session = request.getSession();
> ・
>
>         if ( session.getAttribute(requestURL) == null) {
>             RefreshHandler handler = new RefreshHandler();
>             handler.attach(response);
>
>             CometEngine engine = CometEngine.getEngine();
>             CometContext context = engine.getCometContext(contextPath);
>
>             try {
>                 context.addCometHandler(handler);
>                 session.setAttribute(requestURL, true );
>             } catch (IllegalStateException ex) {
>                    ・
>             }
>         }
>     }
>
>     protected void processRequest(HttpServletRequest request, HttpServletResponse response)
>         throws ServletException, IOException {
>
>         /* Notify RefreshHandler */
>
>         CometEngine engine = CometEngine.getEngine();
>         CometContext<?> cctx = engine.getCometContext(contextPath);
>         cctx.notify(null);
>
>         refreshComboBox(response);
>     }
>
>     private class RefreshHandler implements CometHandler<HttpServletResponse> {
>
>         private HttpServletResponse response;
>
>         public void onEvent(CometEvent event) throws IOException {
>             if (CometEvent.NOTIFY == event.getType()) {
>
>                 refreshComboBox(response);
>
>                 // commented out the resume if it is Http Streaming
>                 //event.getCometContext().resumeCometHandler(this);
>             }
>         }
>           ・.
>     }
>
>     void refreshComboBox(HttpServletResponse response) throws IOException{
>
>         ProvisioningDocLocatorBeanWS provBean = new ProvisioningDocLocatorBeanWS();
>
>         PrintWriter writer = response.getWriter();
>         try {
>
>             writer.write(provBean.getProvisioningDocs());
>             writer.flush(); //Does not flush data to the client
>
>         } catch (JSONException jex) {
>             logger.error(jex.getMessage(), jex);
>         } finally {
>             writer.close(); //Unless I call close data does not get flushed out to client
>             logger.info("Refreshed Client :" + System.currentTimeMillis());
>         }
>     }
>
>
> From:Richard Corbin [mailto:igf1_at_yahoo.com]
> Sent: Friday, October 31, 2008 9:26 AM
> To: Hiram Coffy
> Subject: Re: Problem with comet-based web app **SOLVED**
>
> Hey no problem man, let me know if I can help.
>
> -- Richard
>
>
> ________________________________
>
> From:Hiram Coffy <hiram.coffy_at_pb.com>
> To: Richard Corbin <igf1_at_yahoo.com>
> Sent: Friday, October 31, 2008 7:31:34 AM
> Subject: FW: Problem with comet-based web app **SOLVED**
> Hello Richard,
>
> Thanx much for all your pointers. I hadn稚 realized that you had replied to my post until this morning. I am still getting use to getting around in the forum.
> Apologies for the belated response. Your contributions were very helpful to me. I believe I have the mechanics of this technique down, I just needed clarifications on implementation.
>
> Best Regards,
>
> Hiram
>
> From:Hiram Coffy
> Sent: Thursday, October 30, 2008 4:23 PM
> To: 'users_at_grizzly.dev.java.net'
> Subject: RE: Problem with comet-based web app
>
> Salut Jeanfrancois,
>
> Thank you very much for being so
>  responsive.  Your answers to step 2 and 6 got me unstuck. I appreciate your help.
>
> 2) API: What compatible comet api should I compile the source code against?
>
> If you plan to deploy against v2ur2, you need to build against the API
> that ship with v2. Hence, in your classpath, you need to add:
>
> ${glassfish.home}/lib/appserv-rt.jar.
>
> 6)Last question: Does the app server come bundled with the comet api
>
> yes, in v2: com.sun.enterprise.web.connector.grizzly.comet.*
> in v3 and grizzly standalone: com.sun.grizzly.comet
>
>
> Keep up the great work!
> Regards,
>
> Hiram
>
> A+
>
>
> From:Hiram Coffy
> Sent: Wednesday, October 29, 2008 12:57 PM
> To: 'users_at_grizzly.dev.java.net'
> Subject: Re: Problem with comet-based web app
>
> Hi all,
>
> I am still struggling with getting my comet-based servlet to work. Any help would be much appreciated
>
> I guess what would be helpful for me is a simple step-by-step procedure. So far, what I have found is scattered and fragmented bits and pieces of information, critical details appear to be glossed over.
>
> I suppose I can boil down my request to the following set of questions:
> 1) APP SERVER: What do I need to get comet-based servlet working? e.g. glassfish V2UR2 with comet support enabled.
>
> 2) API: What compatible comet api should I compile the source code with?
>
> 3) API DOWNLOAD: Where do I download the API from?
>
> 4) CLASSPATH: Do I copy the API to ${as.home}/lib for example
>
> 5) TEST: How do I positively verify that comet support is indeed enabled?
>
> 6)Last question: Does the app server come bundled with the comet api
>
> From:Hiram Coffy
> Sent: Sunday, October 26, 2008 5:39 PM
> To: 'users_at_grizzly.dev.java.net'
> Subject: Problem with comet-based web app
>
> Hello Jean-Francois,
> Let me start by thanking you for all your good work on the Grizzly/comet front. Kudos!
>
> I wrote a simple comet app based on the tutorial found at: http://docs.sun.com/app/docs/doc/820-4496/ggrgt?a=view.
> I get the following exception when a request is received by my servlet:
> java.lang.IllegalStateException: Make sure you have enabled Comet or make sure the Thread invoking that method is the same a the request Thread.
> at com.sun.grizzly.comet.CometContext.addCometHandler(CometContext.java:263)
> at com.sun.grizzly.comet.CometContext.addCometHandler(CometContext.java:311)
> ....
> I have already combed through your blogs. I have tried all the suggestions that I have come across. So far, i haven't had any success. I noticed a peculiar behavior with my glassfish instance; whether <property name="cometSupport" value="true"/> is added to domain.xml or not seems to make not a bit of a difference.
> I have made sure to bounce the server every time i make a change to domain.xml to ensure that the changes will be recognized. I have tried several experiments with the "cometSupport" property added and removed from domain.xml. I ave tried similar experiments with <property name="proxiedProtocols" value="ws/tcp"/> I have even enabled the flag through the command line using "asadmin set server.http-service.http-listener.http-listener-1.property.cometSupport=true"
> Relevant environment information:
> Sun Java System Application Server 9.1 (build b58g-fcs) (GlassfishV2)
> excerpt from domain.xml:
> ....
> <http-listener acceptor-threads="1" address="0.0.0.0" blocking-enabled="false" default-virtual-server="server" enabled="true" family="inet" id="http-listener-1" port="8080" security-enabled="false" server-name="" xpowered-by="true">
> <property name="proxiedProtocols" value="ws/tcp"/>
> <property name="cometSupport" value="true"/>
> </http-listener>
> ....
> excerpt from app server output console:
> ....
> WEB0302: Starting Sun-Java-System/Application-Server.
> Enabling Grizzly ARP Comet support.
> WEB0712: Starting Sun-Java-System/Application-Server HTTP/1.1 on 8080
> ....
> excerpt from web.xml
> ...
> <servlet>
> <servlet-name>RefreshServlet</servlet-name>
> <servlet-class>package.RefreshServlet</servlet-class>
> <load-on-startup>0</load-on-startup>
> </servlet>
> ...
> The following bit of code registers the handler and is invoked only once, upon receipt of first request.
> private void registerHandler(HttpServletResponse response)
> throws IllegalStateException{
> if (oneShot) {
> RefreshHandler handler = new RefreshHandler();
> handler.attach(response);
> CometEngine engine = CometEngine.getEngine();
> CometContext context = engine.getCometContext(contextPath);
> try{
> context.addCometHandler(handler);
> oneShot = false;
> }catch(IllegalStateException ex){
> logger.error(ex.getMessage(), ex);
> throw ex;
> }
> }
> }
> Two questions:
> 1) Any suggestions?
> 2) How can I positively tell that comet support is indeed enabled?
>
>
>