DWR Reverse Ajax

目前項目中的消息推送使用了DWR的Reverse Ajax,因此瞭解一下html

Direct Web Remoting

Reverse Ajax

(http://directwebremoting.org/dwr/documentation/reverse-ajax/index.html)java

Index

the ability to asynchronously send data from a web-server to a browser.web

DWR supports 3 methods of pushing the data from to the browser: Piggyback, Polling and Comet.ajax

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.xmlsession

<!-- 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>

頁面加載事件中增長:app

<!-- 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:async

  • Early Closing Mode (Default)
  • Full Streaming Mode (Not available to IE users)
  • Polling Mode

General Configuration

同上面Getting Started配置ide

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, filter, 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, filter, 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:

  1. Extend the DWR servlet and execute the above code after the DWR servlet initializes.
  2. Execute the above code in a servlet that has a <load-on-startup /> value larger than the DWR servlet

Load Monitors

Non-Web Threads and Reverse Ajax

相關博文:

dwr3實現消息精確推送詳細步驟

Web端服務器推送技術原理分析及dwr框架簡單的使用

dwr3 ActiveReverseAjax實現ScriptSession過濾發送到指定客戶端

相關文章
相關標籤/搜索