目前项目中的消息推送使用了DWR的Reverse Ajax,所以了解一下
Direct Web Remoting
Reverse Ajax
(http://directwebremoting.org/dwr/documentation/reverse-ajax/index.html)
Index
the ability to asynchronously send data from a web-server to a browser.
DWR supports 3 methods of pushing the data from to the browser: Piggyback,Polling and Comet.
By default DWR starts with active Reverse Ajax turned off,allowing only the piggyback transfer mechanism.
Getting Started with Reverse Ajax
要使用Reverse Ajax,需要配置WEB-INF/web.xml:
<!-- lang: xml --> <servlet> <servlet-name>dwr-invoker</servlet-name> <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class> <init-param> <param-name>activeReverseAjaxEnabled</param-name> <param-value>true</param-value> </init-param> ... </servlet>
<!-- lang: js --> dwr.engine.setActiveReverseAjax(true);
Configuring Reverse Ajax
Reverse Ajax works in two basic modes,Active and Passive.
Active Reverse Ajax can be broken down into three modes:
- Early Closing Mode (Default)
- Full Streaming Mode (Not available to IE users)
- Polling Mode
General Configuration
同上面Getting Started配置
Early Closing Mode[当前项目中使用]
In Early Closing Mode,DWR holds connection open as in Full Streaming Mode,however it only holds the connection open for 60 seconds if there is no output to the browser. Once output occurs,DWR pauses for some configurable time (maxWaitAfterWrite) before closing the connection,forcing proxies to pass any messages on. To use Early Closing Mode in DWR 2.0.4 and onwards,no configuration is needed - this is the default. The maxWaitAfterWrite parameter defaults to 500 (ms) and can be configured through an init-param on the DWRServlet:
<!-- lang: xml --> <init-param> <param-name>maxWaitAfterWrite</param-name> <param-value>1000</param-value> </init-param>
In this case DWR will keep the connection open for an additional 1000 milliseconds after the first output in case there is additional output,before forcing a flush by closing the connection and requesting it to be re-opened.
The downside of Early Closing Mode is that for applications with a high rate of output,it can cause a large hit count. This can be countered by increasing the pause by setting maxWaitAfterWrite=1000 or similar.
In applications with a large number of connected browsers that are simultaneously sent a message they are likely to all try to reconnect at the same time. If you are affected by this type of problem,then you should consider investigating Full Streaming Mode.
A future version of DWR might attempt to automatically detect buggy proxies.
Full Streaming Mode (not available in IE)
Polling Mode
Error/Retry Logic
Reverse Ajax Hints and Tips
ScriptSession lifecycle - About the creation and invalidation of ScriptSessions.
ScriptSessions are created when /dwr/engine.js is included in a page. By default,the lifecycle is maintained by the org.directwebremoting.impl.DefaultScriptSessionManager.
Non DWR Threads - Passing request information to a non-dwr thread.
WebContextFactory().get().getScriptSession()
will return null in non-dwr threads.
ScriptSessionManager - Obtaining the ScriptSessionManager from a non-dwr thread.
<!-- lang: java --> Container container = ServerContextFactory.get().getContainer(); ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
Scaleability - One thread per application is scalable,one thread per request is not.
Most reverse ajax implementations require a separate thread to push data to clients. Spawning a separate thread for each DWR request is not scalable. We recommend that you use a thread pool in combination with an application scoped DWR creator,please see the Clock example in the dwr.war for a sample implementation.
Browser API - How to target specific ScriptSessions.
DWR's Browser API contains several useful methods for updating browsers. Several of the Browser methods take a ScriptSessionFilter which will allow you to target specific ScriptSessions based on their attributes (see Setting differentiating attribute(s) on a ScriptSession).
How to use the Browser API with a ScriptSessionFilter to differentiate users:
1.Implement a ScriptSessionFilter:
<!-- lang: java --> public class TestScriptSessionFilter implements ScriptSessionFilter { public TestScriptSessionFilter(String attributeName) { this.attributeName = attributeName; } public boolean match(ScriptSession session) { Object check = session.getAttribute(attributeName); return (check != null && check.equals(Boolean.TRUE)); } private String attributeName; }
2.Set an attribute on the ScriptSession:
<!-- lang: java --> // Add the attribute into the ScriptSession sometime before using the Filter. ScriptSession scriptSession = WebContextFactory.get().getScriptSession(); String attributeName = "attr"; scriptSession.setAttribute(attributeName,true);
Note - this must be done from a DWR initiated thread (WebContextFactory requires it).
3.User the ScriptSessionFilter in your reverse AJAX thread:
<!-- lang: java --> ScriptSessionFilter filter = new TestScriptSessionFilter(attributeName); Browser.withPageFiltered(page,filter,new Runnable() { public void run() { // Call a method on DWR's Util class which sets the value on an element on your HTML page with a id of "divID". Util.setValue("divID","value of div"); } });
Or call a named function:
<!-- lang: java --> ScriptSessionFilter filter = new TestScriptSessionFilter(attributeName); Browser.withPageFiltered(page,new Runnable() { public void run() { // Call a named function from your html page. Note - The ScriptsSessions.addFunctionCall will only // send the function call to ScriptSessions matching TestScriptSessionFilter. ScriptSessions.addFunctionCall("yourJavaScriptFunctionName",arg1,arg2,etc.); } });
Or add some arbitrary script:
<!-- lang: java --> ScriptSessionFilter filter = new TestScriptSessionFilter(attributeName); Browser.withPageFiltered(page,new Runnable() { public void run() { // Add script which will modify the document.title. on your html page. ScriptSessions.addScript(("document.title = 'My new title,from DWR reverse AJAX!';")); } });
It is important to note that a few Browser methods require a WebContext which require that the requests come from a DWR thread. Currently the methods that require this are - withCurrentPageFiltered,withCurrentPage,and getTargetSessions. All other methods can be called safely from non-DWR threads.
ScriptSession - Setting differentiating attribute(s) on a ScriptSession.
One of the most common ways to differentiate users on the same page is by setting attributes on the ScriptSession and getting them in a reverse ajax thread. Since the ScriptSession does not exist until engine.js is included this cannot be done in an MVC controller or a JSP.
There are currently two best practices for setting an attribute on the ScriptSession:
1.Call a remote DWR method:
<!-- lang: java --> /** * This method should be remoted via DWR and generally called before reverse ajax is initialized. * You may choose to call this method when your page which uses reverse AJAX is initialized,then * in your callback function you may initialize reverse AJAX (dwr.engine.setActiveReverseAjax(true);) * and be certain that the */ public void remoteMethod() { String value = "someValue"; // this may come from the HttpSession for example ScriptSession scriptSession = WebContextFactory.get().getScriptSession(); scriptSession.setAttribute("key",value); }
2.Use a ScriptSessionListener which will be notified when ScriptSessions are created and destroyed. Please see the ScriptSessionListener section for more details.
<!-- lang: java --> /** * When a ScriptSession is created set a "userId" attribute on the ScriptSession. * In this case userId is an attribute you have set on the HttpSession that uniquely * identifies the user. */ public void sessionCreated(ScriptSessionEvent ev) { HttpSession session = WebContextFactory.get().getSession(); String userId = (String) session.getAttribute("userId"); ev.getSession().setAttribute("userId",userId); }
Once the ScriptSession has been populated with an attribute,a reverse ajax thread can use the Browser API (recommended) with a ScriptSessionFilter to target specific ScriptSessions or retrieve the attribute from the ScriptSession (scriptSession.getAttribute()) to differentiate the users.
ScriptSession - Differentiating ScriptSessions by request parameters.
ScriptSessionListeners - How to use ScriptSessionListeners to take actions when a ScriptSession is created or destroyed.
ScriptSessionListeners allow you to take action when a ScriptSession has been created or destroyed. To configure a ScriptSessionListener you will need to execute code similar to the following:
<!-- lang: java --> Container container = ServerContextFactory.get().getContainer(); ScriptSessionManager manager = container.getBean(ScriptSessionManager.class); ScriptSessionListener listener = new ScriptSessionListener() { public void sessionCreated(ScriptSessionEvent ev) { HttpSession session = WebContextFactory.get().getSession(); String userId = (String) session.getAttribute("userId"); ev.getSession().setAttribute("userId",userId); } public void sessionDestroyed(ScriptSessionEvent ev) { } }; manager.addScriptSessionListener(listener);
It is important to note that ScriptSessionListeners must be added after DWR has initialized. There are generally two ways to do this:
- Extend the DWR servlet and execute the above code after the DWR servlet initializes.
- Execute the above code in a servlet that has a
<load-on-startup />
value larger than the DWR servlet