RSS

DWR Learning VIII

20 Apr

Turning the Tables: Reverse Ajax

Reverse Ajax is something fairly new, coming to prominence only really in the past few months. The basic concept is that instead of the client having to pull information from the server, the server can push information directly to the client.(很神奇,不是客户端向服务器写信息,二是服务器向客户端直接写信息!) This is all in an effort to overcome the one limitation of Ajax that persists from the “classic” web model, namely that real-time information is still not technically possible because there is still a requirement that the client has to contact the server and ask “Hey, dummy, has anything changed?” and then update the page (or some section thereof) if anything has. While you can do this quickly enough to make it seem it’s happening in real time, it really isn’t. What’s needed is a way for the server to contact all the browsers viewing its contents to announce when changes have occurred. There are three techniques at present for performing this magic trick, and DWR supports all three.  

non-Ajax applications

Polling

The polling technique is, strictly speaking, not specific to Ajax and has been around for quite some time longer. This is the simplest form of “server push,” and you are no doubt already aware of it, even if the term is unfamiliar.polling就是隔一段时间向服务器发送一request来检查服务端是否有数据更新。 In this diagram, you can see the first three poll events occur without any user intervention; the polling client, or Ajax engine, is initiating the request independent of anything the user may be doing. Also note that those first three poll events do not make calls to the browser UI when the response is received. This indicates cases where no update was required because no new data was present.

Comet

The basic concept is very simple: a client makes a request to the server, and the server starts to respond. It then continues to respond at an exceedingly slow pace. So slow in fact that the connection is held open indefinitely because, while it’s moving very slowly, it’s still active. Think about that: we’ve essentially gotten around the HTTP limitation of not having a static connection from client to server! Data can then flow across this connection in both directions, giving you the potential for truly real-time updates initiated by the server. Comet就是一个长http请求,在请求期间服务端可以向客户端push数据,但是这种做法要求server和brower长期建立一个通信通道,而且效率很低.

Piggybacking

This approach is the one “passive” method offered by DWR. It is called passive because there is a user interaction that is required to make it work (it actually could work without user interaction in theory, but it typically doesn’t). In fact, for all intents and purposes, piggybacking and passive reverse Ajax are pretty much synonymous since there really isn’t any other form of passive reverse Ajax that I’m aware of. Piggybacking就是服务端的更新数据都在排队等待,等到下一次有请求过来,那么这些等待更新数据就伴随这次请求一起发送到brower. The benefit to piggybacking as a passive technique is that no additional load is placed on the server. The server is still awaiting a request from the client, but the difference is that the server will have been queuing responses all along, and will send them as appropriate with the next request from the client. It’s a very opportunistic way of doing reverse Ajax, but it doesn’t have the potential to overload your server (unless so many events are being queued up that you run out of memory, but that’s a whole other concern). DWR will use piggybacking by default if you don’t tell it to otherwise, so by default you don’t run any risk of overloading your server by turning reverse Ajax on. Speaking of which, how exactly do we turn reverse Ajax on, and configure the various options I mentioned and make use of it in code? As I’ve said on numerous occasions throughout this text, DWR makes it very simple indeed; let’s see how now. The Code of Reverse Ajax The first step to implementing reverse Ajax with DWR is some new configuration elements. First, in web.xml, a few new init parameters to the DWR servlet are required:  

 1: <init-param>
 2: <param-name>activeReverseAjaxEnabled</param-name>
 3: <param-value>true</param-value>
 4: </init-param>
 5: <init-param>
 6: <param-name>org.directwebremoting.extend.ServerLoadMonitor</param-name>
 7: <param-value>org.directwebremoting.impl.PollingServerLoadMonitor</param-value>
 8: </init-param>
 9: <init-param>
 10: <!-- This should be disconnectedTime, but because of a bug prior to the -->
 11: <!-- as yet unreleased 2.0.2, you have to use timeToNextPoll. Use -->
 12: <!-- disconnectedTime from 2.0.2 onward. -->
 13: <param-name>timeToNextPoll</param-name>
 14: <param-value>1000</param-value>
 15: </init-param>

In fact, only the first, activeReverseAjaxEnabled, is actually required. The other two are optional (the second, the ServerLoadMonitor one, is required if you’re using the polling technique). Also, take note of the comments related to the timeToNextPoll parameter. You are actually supposed to use the parameter named disconnectedTime, but because of a bug in versions of DWR prior to 2.0.2 (which has not been released at the time of this writing), you have to use timeToNextPoll instead. Keep this in mind if you upgrade DWR versions, lest you have busted applications and a headache from trying to figure out why. With this configuration out of the way, one new bit of JavaScript is required on the page that will be involved with the reverse Ajax, and it is simply this: dwr.engine. setActiveReverseAjax(true);. This, plus the configuration in web.xml, is all that’s required to activate reverse Ajax. The next part of the equation is actually doing something with those polling requests. The way you do this is by writing some code on the server that updates the session for each client attached to the server. DWR keeps track of each client that contacts it by storing a session for each. This is different from the usual HTTP session (although I wouldn’t be surprised if the HTTP session is in fact used internally by DWR). With it you can call JavaScript code, and the next poll request that comes in will be notified of those calls. Let’s look at that code now:  

1 String currentPage = wContext.getCurrentPage();
2
Collection sessions = wContext.getScriptSessionsByPage(currentPage);
3
Util utilAll = new Util(sessions);
4 utilAll.setValue("divTest", d.toString(), true);

Yes, that’s really it! Once you have the name of the current page (as known to DWR), you can get a list of all the sessions currently connected to that page. You can then get an instance of the Util class, which is the main interaction point in DWR between your Java code and the JavaScript on the client. Passing it the list of sessions allows it to interact with them all without you having to do anything else like iterate over the collection. The Util class exposes a number of convenient methods, one of which is setValue(). This is akin to doing document. getElementById(“divTest”).innerHTML = “”; on the client, but it will take care of the details such as if the target element is a text box or something else. Here, we tell it to update the contents of divTest using the current value of the Date field we record, and we also specify that we want any HTML escaped by passing true as the third parameter so we don’t break anything client side (not really a risk here, but better safe than sorry). If you don’t want to use the polling approach, you can switch to Comet very easily: simply comment out the following element in web.xml:  

 1: <init-param>
 2: <param-name>org.directwebremoting.extend.ServerLoadMonitor</param-name>
 3: <param-value>org.directwebremoting.impl.PollingServerLoadMonitor</param-value>
 4: </init-param>

Since Comet is the default, that’s all you need to do to activate it. Finally, to see the piggyback technique in action, you have only to remove the following elements from web.xml:  

 1: <init-param>
 2: <param-name>
 3: activeReverseAjaxEnabled
 4: </param-name>
 5: <param-value>
 6: true
 7: </param-value>
 8: </init-param>
 9: <init-param>
 10: <param-name>
 11: org.directwebremoting.extend.ServerLoadMonitor
 12: </param-name>
 13: <param-value>
 14: org.directwebremoting.impl.PollingServerLoadMonitor
 15: </param-value>
 16: </init-param>
 17: <init-param>
 18: <!-- This should be disconnectedTime, but because of a bug prior to the -->
 19: <!-- as yet unreleased 2.0.2, you have to use timeToNextPoll. Use -->
 20: <!-- disconnectedTime from 2.0.2 onward. -->
 21: <param-name>
 22: timeToNextPoll
 23: </param-name>
 24: <param-value>
 25: 1000
 26: </param-value>
 27: </init-param>

Then, somewhere in index.jsp, add the following:  

<input type="button" onClick="RemoteClass.startPolling();">

Now if you load up the page, you’ll notice that the time doesn’t automatically change. However, each time you click the button, you’ll see that it does change. This is in spite of the fact that the code that does the updating is not executed when you call the startPolling() method. What’s happening is that the thread continues to run and queues up changes to the time. Then, when the next DWR request comes in as a result of the button press, DWR pounces on its opportunity to send the necessary updates that have been building up in the queue. This is obviously the least responsive way to do reverse Ajax, but it’s also the least worrisome from a server load perspective, and so might be desirable when truly real-time updates are not actually required (this is often called “good enough” time).

 
Leave a comment

Posted by on 04/20/2008 in DWR

 

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.