Reverse Ajax Hints and Tips

Script session lifecycle

Script sessions are created when /dwr/engine.js is included by a page. By default, the lifecycle is maintained by the org.directwebremoting.impl.DefaultScriptSessionManager.

If you call the following javascript function

dwr.engine.setNotifyServerOnPageUnload(true);

A remote DWR call will be made to the ScriptSessionManager when the page is unloaded, informing it to invalidate the script session. By default, ScriptSessions will be invalidated using a timeout strategy.

Passing request information to a non-dwr thread

Non-dwr threads have no reference to the dwr-thread that created them. Because of this, WebContextFactory().get().getScriptSession() will return null in a non-dwr thread. You will therefore need to pass data from DWR threads to non-DWR threads such as the ScriptSession in the example below.

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, instead you should use a single thread for reverse ajax.

Eg:
public class RemoteClass {
   /**
    * Remote DWR method called by a client to initiate the server push.
    */
   public void startReverseAjax () {
      ReverseAjaxThread thread = ReverseAjaxThread.getInstance();
      thread.addScriptSession(WebContextFactory.get().getScriptSession());
   }
}

public class ReverseAjaxThread {
   private static ReverseAjaxThread INSTANCE;
   private Set<ScriptSession> scriptSessions = new HashSet<ScriptSession>();
   
   public synchronized ReverseAjaxThread getInstance() {
      if (INSTANCE == null) {
         INSTANCE = new ReverseAjaxThread();
         INSTANCE.start();
      }
      return INSTANCE;
   }
   
   public void run() {
      while (true) {
         for (ScriptSession scriptSession : scriptSessions) {
            if (scriptSession.isValid()) {
               // alert('hello')
               new ScriptProxy(scriptSession).addFunctionCall("alert", "hello");
            } else {
               synchronized (this) {
                  scriptSessions.remove(scriptSession);
               }
            }
         }
         Thread.sleep(10000); // sleep for 10 seconds
      }
   }
   
   public synchronized void addScriptSession(ScriptSession scriptSession) {
      // use a copy so that code reading from scriptSessions does not need to be synchronized
      Set<ScriptSession> scriptSessionsCopy = new HashSet<ScriptSession>(scriptSessions);
      scriptSessionsCopy.add(scriptSession);
      scriptSessions = scriptSessionsCopy;
   }
}

Differentiating ScriptSessions by attributes

One of the most common ways to differentiate users on the same page is by setting attributes on the script session and getting them in a reverse ajax thread. Since the script session does not exist until engine.js is included this cannot be done in an MVC controller or a JSP, please vote for this issue http://bugs.directwebremoting.org/bugs/browse/DWR-320 to resolve this.

There are currently two best practices for populating the script session on page load.

1. Call a remote DWR method on page load which populates the script session:

public void remoteMethod() {
   String value = "someValue"; // this may come from the HttpSession for example
   ScriptSession scriptSession = WebContextFactory().get().getScriptSession();
   scriptSession.setValue("key", value);
}

2. Add a script session listener which will be notified when script sessions are created and destroyed.

Container container = ServerContextFactory.get().getContainer();
ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
ScriptSessionListener listener = new ScriptSessionListener() {
   public void sessionCreated(ScriptSessionEvent ev) {
      HttpSession session = WebContextFactory.get().getHttpSession();
      ScriptSession scriptSession = ev.getScriptSession();
      String userId = (String) session.getAttribute("userId");
      scriptSession.setAttribute("userId", userId);
   }
   
   public void sessionDestroyed(ScriptSessionEvent ev) {}
};
manager.addScriptSessionListener(listener);
Once the script session is populated, a reverse ajax thread can call scriptSession.getAttribute() to differentiate the users.

Differentiating ScriptSessions by request parameters

Another way to differentiate users on the same page is by the request parameters. In order to use this functionality, you must set the normalizeIncludesQueryString init-param to true (http://directwebremoting.org/dwr/server/servlet). Once this is enabled, the script session manager will include request parameters in the page name.
scriptSessionManager.getScriptSessionsByPage("/page?foo=bar")

If you would like to plug-in your own algorithm to determine the page name, you will need to implement your own org.directwebremoting.extend.PageNormalizer and plug it in using DWR's dependency injection mechanism in web.xml.

<servlet>
  <servlet-name>dwr-invoker</servlet-name>
  <display-name>DWR Servlet</display-name>
  <description>Direct Web Remoter Servlet</description>
  <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
  <init-param>
       <param-name>org.directwebremoting.extend.PageNormalizer</param-name>
       <param-value>foo.bar.MyPageNormalizer</param-value>
  </init-param>
</servlet>

Obtaining the script session manager from a non-dwr thread

The script session manager can be obtained using the following code:

Container container = ServerContextFactory.get().getContainer();
ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);