Pushlets: Send events from servlets to DHTML client browsers

Discover how pushlets, a servlet-based notification mechanism, enables server-side Java objects to call back JavaScript code within a client browser.

Page 2 of 3

That question hits an issue that has inhibited many developers from embracing DHTML. First, the browser must be version 4.0 or later. The official standards body for DHTML-related specifications is the World Wide Web Consortium (W3C). However, the Microsoft and Netscape browsers now each have proprietary DHTML extensions that you must track as well.

Luckily, the situation has become much better. Most users by now have the later browsers, and some people (in particular, the "Dannymen," Dan Steinman and Danny Goodman, see Resources) have done good work on cross-browser DHTML libraries that you can reuse. As a Java programmer, you may appreciate the fact that you can do reasonably clean object-based or even object-oriented programming in JavaScript. You will find examples in my basic DHTML demos (www.fluidiom.com:8080; choose Examples, then DHTML), but it is worthwhile to check the DHTML resources. Once you have browser issues hidden behind a facade of cross-browser libraries, DHTML programming becomes real fun.

So, with Java gaining more market presence on the server and with DHTML providing powerful features on the client, I decided to couple those two great technologies in a direct way, using pushlets. For that I implemented a lightweight framework for the server and some DHTML libraries for the client. These are discussed next.

A pushlet framework

The pushlet framework allows clients to subscribe to subjects within a server from which they subsequently receive events. The framework's basic design pattern -- Publish, Subscribe -- has both server and client components:

  1. A server-side collection of Java classes designed around the Publisher class and Subscriber interface (see Figure 1, a Unified Model Language, or UML, class diagram)
  2. A client-side reusable JavaScript library (pushlet.js) and HTML (pushlet.html) for receiving events within DHTML clients and passing them to the application
  3. Client-side Java classes (JavaPushletClient.java and JavaPushletClientListener.java) for receiving events within Java clients
  4. Cross-browser DHTML client utility libraries (layer.js, layer-grid.js, and layer-region.js) for displaying content in DHTML layers
  5. Generators for test events (EventGenerators.java) and example applications (www.fluidiom.com:8080; choose Pushlet, then Examples)

Server-side class design

On the server side, the key classes are the Pushlet servlet, the Publisher class, the Subscriber interface, and the Event class, as seen in Figure 1.

Figure 1. UML class diagram: Server-side pushlet framework

By invoking the Pushlet servlet through an HTTP request, clients subscribe to receive Events. The request indicates:

  1. For which subject they would like to receive Events
  2. In which format -- JavaScript calls (the default), XML, or Java-serialized objects -- Events should be sent
  3. Through which receiver protocol (currently HTTP only; additional protocols in future versions) they should be sent

A sample request for receiving Amsterdam Exchange (AEX) stock rates formatted as XML through an HTTP response stream would be:

http://www.fluidiom.com:8080/servlet/pushlet?subject="/stocks/aex"&format="XML"&protocol="HTTP-stream"

Subject identifiers are organized as a hierarchical topic tree. For example, /stocks identifies all Events related to stock rates, while /stocks/aex identifies stock rates for the Amsterdam Exchange. Likewise, the subject / indicates all events.

Currently, the only receiver protocol is a client HTTP response stream. In a future extension other receiver protocols such as TCP, UDP, RMI, HTTP POSTing, or even SMTP (email) will be able to be specified.

An Event is merely a set of name-value string pairs (implemented with java.util.Properties). The Publisher has an interface through which classes that generate Events can publish them. The Publisher keeps a list of Subscribers and sends each Event to those Subscribers whose subject matches the Event's subject. Events may be originating within the server through EventGenerators, which may listen to external events such as a stock feed. In addition, clients may publish Events through HTTP with the Postlet servlet. The responsibilities of other classes in Figure 1, PushletSubscriber and its contained classes, can best be explained through scenarios.

Figure 2 shows a UML sequence diagram for a client browser subscribing for Events from the Publisher.

Figure 2. UML sequence diagram: Client event subscription

Pushlet is invoked with the servlet doGet() method. Because multiple clients may invoke the same Pushlet object, it should itself not be a Subscriber. Instead, it delegates all subscription (and subsequent Event handling) by creating a new PushletSubscriber object for each doGet() and letting it run until finished with eventLoop(). The PushletSubscriber that implements the Subscriber interface registers for Events with the Publisher through the join() method. To deal with different client formats and protocols, it creates a specialized ClientAdapter object, in this case a BrowserPushletAdapter. For browsers supporting Multipart MIME, such as Netscape 4.0 or later, this would be a MultipartBrowserClientAdapter. The final call in this scenario is a "wait for event loop." Note that the deQueue() method suspends the execution of the current thread until an Event becomes available (indicated with a half arrow). This is explained through the next scenario in Figure 3.

Figure 3. UML sequence diagram: Event publishing and client notification

The scenario illustrated in Figure 3 shows how an EventGenerator creates an Event and calls Publisher.publish() to have it dispatched. The Publisher walks through its list of Subscribers and asks each whether the Event matches its subscription criteria (currently only the subject). If the Event matches, Publisher calls the send() method on the Subscriber. Each PushletSubscriber object has a GuardedQueue object in which it queues incoming Events when send() is called.

So why doesn't Publisher directly push the Event to the BrowserPushletAdapter? First, we want to suspend execution of the BrowserPushletAdapter thread until an Event becomes available; that is, we don't want to do a busy-wait (also known as polling). Second, a Publisher may notify multiple clients. Having a synchronous send() method call a slow client on the other end of the line may block all the other clients to be notified next. This is a design pitfall I also observed in RMI or CORBA callback examples in which a list of clients is called back synchronously -- client number 13 on a slow connection and a 386 processor may block the remaining callbacks.

The GuardedQueue, a utility object, uses the readers-writers pattern to enqueue or dequeue Objects, with guarded suspension using the java.lang.Object.wait() and notifyAll() methods. The thread of a client of GuardedQueue calling deQueue() is suspended (using wait()) until an Object is queued. Likewise, a client enqueuing an Object will be suspended as long as the queue is full. When the clients are fast enough, the GuardedQueue never fills up.

After the BrowserPushletSubscriber has dequeued an Event object, it calls the push() method on the BrowserPushletAdapter, which formats the Event to a JavaScript element and send it to the browser. For example, for a Royal Philips Electronics stock rate of 123.45, the JavaScript element would look like this:

<SCRIPT language=JavaScript >parent.push('subject', '/stocks/aex', 'philips', '123.45')</SCRIPT>

Now we have arrived at the client browser side. We assigned the Pushlet itself to a hidden HTML frame. Within this frame, parent.push() calls the push() function in its parent. This is effectively the callback that originated within the server. The parent frame has to implement the push() method. Since this is a common task for all browser clients, two reusable files for the client are provided: pushlet.html and pushlet.js.

pushlet.html is meant to be included in a frame within the application-specific client HTML document. It can be made a parameter with the subject identifier and a background color (such that it remains invisible). Most important, it implements the JavaScript push() method as follows:

function push() { // Create a PushletEvent object from the arguments passed in. // push.arguments is event data coming from the Server pushletEvent = new PushletEvent(push.arguments)

// Show blinking light as data is coming in updateStatusFrame();

// Is parent ready to receive events? if (!parent.onPush) { return; }

// Forward the event to the parent frame that should do // application-specific handling of the event parent.onPush(pushletEvent); }

The function push() first creates a JavaScript object from the parameters passed in. Yes, you can do object-based programming in JavaScript. Reminiscent of varargs in C/C++, JavaScript functions may have a variable number of arguments. A PushletEvent object is created with whatever arguments were passed to the push() method from the server. Let's take a look at PushletEvent implemented in pushlet.js:

/* Object to represent nl.justobjects.pushlet.Event in JavaScript. Arguments are an array where args[i] is name and args[i+1] is value. */ function PushletEvent(args) { // Member variable setup; the Map stores the N/V pairs this.map = new Map();

// Member function setup this.getSubject = PushletEventGetSubject this.put = PushletEventPut this.get = PushletEventGet this.toString = PushletEventToString this.toTable = PushletEventToTable

// Put the arguments' name/value pairs in the Map for (var i=0; i < args.length; i++) { this.put(args[i], args[++i] ); } }

// Get the subject attribute function PushletEventGetSubject() { return this.map.get('subject') }

// Get event attribute function PushletEventGet(name) { return this.map.get(name) }

// Put event attribute function PushletEventPut(name, value) { return this.map.put(name, value) }

function PushletEventToString() { return this.map.toString(); }

// Convert content to HTML TABLE function PushletEventToTable() { return this.map.toTable(); }

In turn, pushlet.js uses a Map JavaScript object, a java.util.Hashtable-like utility object.

Next, push() calls the updateStatusFrame() method to show a blinking light to indicate we are still receiving events. If a parent.onPush() function exists, push() calls that function with the PushletEvent. The parent.onPush() method is the application-specific, event-handling function that in this case may update the philips stock-related layer in a DHTML page.

That ends the description of the basic framework design. Now let's turn our attention to situations where we can apply pushlets and look at their strengths and weaknesses.

Pushlet applications

Pushlets allow many types of Web applications to be developed. Since the framework also allows for clients to upload events (through the Postlet), applications can do more than passively push data. Possible applications can be categorized according to:

  • Whether the events originate from the server, from clients, or both
  • Whether state is kept on the server, within the clients, or both

Moreover, since live events are made available to JavaScript, you can create scriptable plug-ins able to receive live updates. You may script your Macromedia Flash or VRML World application, for example.

Let's examine some possible applications.

Monitoring

With pushlets, various data sources -- stocks, weather, votes, flight arrivals, systems -- may be monitored live.

Gaming

Developers can create simple move-display games, such as a two-user tic-tac-toe and more elaborate chess, Risk, and Monopoly-like games. Players can make moves through postlets. An extended server could implement the game's logic and push changes in the game's state to the clients. In an upcoming version of the pushlet framework I plan to implement a generic multiuser session so that the players' presence and actions are communicated.

Distributed Model View Controller (MVC)

MVC refers to a design pattern often found in user-interface frameworks such as Java Swing and Microsoft MFC. In the distributed variant, a model (often the data) resides on a server, while clients hold the views and controls. The model is modified through the controls. The model then notifies all attached views, which subsequently refresh themselves.

Many applications may have a Web frontend through which data on the server is updated by multiple users -- flight reservation systems, for example. If one client makes an update, the others won't see it unless they continuously refresh their pages. In some cases that is a simple and workable solution, but in other cases users need to be synchronized with the update as it happens. That is easily done with pushlets pushing a URL as the single event. When a client receives the URL, it refreshes the page.

Enterprise JavaBeans (EJBs) server is an example. Although Java clients are able to talk directly to an EJB (through RMI or CORBA), more often servlets and JSPs are used as a frontend. In this case notification becomes much harder. With pushlets, an EJB could notify its attached Web clients whenever its state changes.

Web presentations

When referring to Web presentations, I once quipped, "Are you being surfed?" In fact, Web presentations, also known as Web tours, inspired me to develop pushlets. I abandoned PowerPoint in making Java course content and developed a content-management framework based on XML with slide presentations in HTML. Since classrooms frequently had no projector but all students had a networked computer, I developed a simple application (WebPres) that enable me to change HTML slides with all students automatically.

| 1 2 3 Page 2