Archive for July 18th, 2006

A Rhino at the Seaside?

A couple of weeks ago I picked up Chris Double’s server-side javascript implementation, which uses the Mozilla Project’s Rhino Javascript environment with Jetty to provide a Javascript-controlled Java Servlet webserver.

The code’s available both for browsing and for darcs download:

darcs get http://www.lshift.net/~tonyg/javascript-server/

After adding support for Jetty’s SessionHandler class to Chris’s example.js, I downloaded the prototype.js Javascript utility library and got it running in a server-side environment1. The next step was using Rhino’s continuation support to implement the equivalent of PLT Scheme’s send/suspend/dispatch (also seen in Seaside, under-the-covers as part of the HTML-rendering and workflow aspects of the system, and in SISCWeb, which is at the core of our Icing library).

Here’s a little workflow, roughly equivalent to Seaside’s Counter application:

sv.addEntryPoint
("/count", // [1]
 function (servlet, bindings) {
     var finalC = servlet.withState
     (10, // [2]
      function (c) { // [3]
          while ( // [4]
                 servlet.sendAndDispatch
                 (function (embedUrl) { // [5]
                      servlet.replyHtml
                      (doc(”Counter”,
                           <>
                             <p>{c.value}</p>
                             <p>
                               <a href={embedUrl(function(){
                                        c.value++; return true})}
                                >More</a>;
                               <a href={embedUrl(function(){
                                        c.value–; return true})}
                                >Less</a>;
                               <a href={embedUrl(function(){
                                        return false})}
                                >Stop</a>;
                             </p>
                           </>));
                  }))
          {
            // Nothing to do in the body of the loop.
          }
          return c.value; // [6]
      });
     servlet.replyHtml(doc(”Bye!”, <p>Bye! {finalC}</p>));
     // [7]
 });

Points of interest:

  • [1] is where we specify the URL path to this workflow.
  • [2] and [3] are about preserving state across use of the back button, about which more below.
  • [4] is the point at which control will resume when the user clicks on any of the links produced by the embedUrl argument to the function given to sendAndDispatch.
  • [5] is the function for producing a document for the user containing links (generated by embedUrl) that cause the workflow to resume at [4].
  • [6] is the point at which one of the embedded link-handlers in [5] has returned false to [4], causing the while-loop to terminate. At this point the state held in c is extracted and the stateful part of the workflow is over.
  • [7] is where the workflow finally ends, because the final document sent to the user wasn’t sent from within sendAndDispatch and didn’t contain any embedded links to a continuation.

Javascript is a little like Scheme - but not enough like Scheme to avoid the pitfalls of using ordinary local variables in a web workflow. The problem is that there are two ways you might want local variables to behave as the user back-and-forwards around your workflow, both perfectly reasonable and appropriate at different times:

  • the contents of variables could be unshared across stages of the workflow, so that backing up and proceeding again from an earlier point can run without being affected by any of the decisions the user has used the back button to, in effect, undo; and

  • the contents could be shared across stages of the workflow, so that the user feels like he or she is affecting some real state in the server, and so that the different pages in the workflow appear to all be affecting this separate real object.

The first option seems to me more functional in style, and the second more object-oriented.

To produce the second, object-oriented effect using these Javascript servlets, simply declare variables as Javascript locals and assign to them. The first is trickier: all variables are mutable, and there’s no pleasant syntax for functional-style rebinding of variables, so I’ve resorted to the withState method seen in the example above.

The basic idea is that we should reify functional variables (since they’re the exception rather than the rule in Javascript; in Scheme, we’d probably reify the mutable ones!) and use a system very much like Scheme’s dynamic-wind to make sure the correct values are visible at each stage in the workflow. Here’s a more focussed example of withState usage:

var finalResult =
  servlet.withState(initialValue,
                    function (stateCell) {
                      // … code using stateCell …
                      return finalValue;
                    });

The initialValue gets placed into a fresh managed cell, which is bound to stateCell for the duration of the function. The code in the function should access and modify stateCell.value, and the values will be tracked automatically across the forward and back buttons. The final result of the function is used as the final result of the whole withState call. Once withState returns, stateCell is no longer automatically tracked - it has gone out of scope, in a way.


Footnote 1: Tricks were required to get prototype.js running - but they were as simple as defining document = {}; window = {}; navigator = {};.

1 comment July 18th, 2006 tonyg

HTML Email is hard to get right

For a recent project, we developed support for sending automatically-generated HTML emails. Now, most people do this by including a message body with MIME-type text/html. For extra points, sometimes there’s also a text/plain part alongside the HTML in a multipart/alternative container.

The problem with doing things this way is that you can’t include any images or other resources (such as CSS) as separate parts of the email linked to from the main HTML body-part. For that, you need to use the multipart/related MIME-type. Unfortunately, few commonly-used email clients render multipart/related HTML-plus-resource aggregations well.

We only tried the arrangement where the multipart/related, containing the main HTML page and its associated resources, was a sibling of the text/plain part within the multipart/alternative container. The inverse arrangement, with the multipart/alternative as the main document within the multipart/related part, was something we have yet to experiment with.

Here’s a picture of the structure of our initial attempts:

multipart/alternative
 |
 +-- text/plain
 +-- multipart/related
      |
      +-- text/html
      +-- image/gif
      +-- text/css

This worked reasonably well in Thunderbird and Outlook 2002, but we had consistent reports from our customer that the images and stylesheet would randomly fail to display in Outlook 2003 (SP2). After lots of mucking around trying to get Outlook to either work reliably or fail reliably, we gave up on that line and instead simplified the structure of our emails, putting the CSS styling inline in the HTML HEAD element:

multipart/alternative
 |
 +-- text/plain
 +-- multipart/related
      |
      +-- text/html (with text/css inline in HEAD)
      +-- image/gif

This didn’t work particularly well, either: it seems many email clients ignore styles set in the HEAD element. Finally, we moved to applying CSS styling inline, using a style attribute on each styled element. We were able to use an XSLT transformation to allow us to write clean HTML and apply the CSS style attribute automatically. The final structure of the emails we sent:

multipart/alternative
 |
 +-- text/plain
 +-- multipart/related
      |
      +-- text/html (with text/css copied on to each element!)
      +-- image/gif

This seems to work more-or-less reliably across

  • Thunderbird
  • Outlook 2002
  • Outlook 2003 SP2
  • Google Gmail
  • MS Hotmail

If I was to do it all again, I’d give serious consideration to the traditional non-multipart text/html solution with images hosted by some public-facing web server. We managed to get our multipart-HTML-emails working acceptably, but only by the skin of our teeth.

References:

4 comments July 18th, 2006 tonyg

Calendar

July 2006
M T W T F S S
« Jun   Aug »
 12
3456789
10111213141516
17181920212223
24252627282930
31  

Posts by Month

Posts by Category