Latest ESXX Release


Sunday, August 24, 2008

More documentation

More documentation has been added to the Wiki; specifically the Request, Response and URI classes have been (rudimentary) documented.

Thursday, August 21, 2008

New binary release

Today, I released the second binary release of ESXX. Go get your favourite server-side JavaScript platfrom while it is still hot!

Thursday, August 7, 2008

JMX support

Today, I added initial JMX support to ESXX, which means that you can now use JConsole or VisualVM (with the MBeans plug-in) to view the loaded server-side Rhino-powered JavaScript applications, plus some statistics about them (when the app was (last re-) started, when it was last active, how many requests the app has handled and wall-clock execution time).

Pretty neat. It's also possible to unload an application from memory using the JMX console.

Dustin's The JMX MXBean and Eamonn McManus's What is an MXBean? were of great help — read them if you're interested in adding JMX to your own projects!

Tuesday, August 5, 2008

Using Apache's mod_cache with CGI or FastCGI

I just though I would elaborate on the caching part in the last post, since I had a real hard time finding good information about this on the web.

Scenario: You have a web application that produces static or semi-static content, and you'd like increase the performance and/or reduce the computing load.

The first thing you need to do is to mark the produced content as cachable. This involves setting the "Last-Modified" and "Expires" HTTP response headers. You should also add a "Cache-Control" header with a "max-age" parameter, and optionally also the "public" flag.

In ESXX, here's how you could construct the headers object, which is used in the Response() constructor, assuming last_modified and expires are dates in milliseconds and max_age is in seconds:

    var headers = {
"Last-Modified": new Date(last_modified).toUTCString(),
"Cache-Control": "max-age=" + max_age + ", public",
"Expires": new Date(expires).toUTCString()
};


Second, it's a good thing to handle the "If-Modified-Since" request header and return HTTP status 304 (Not Modified) if the client's cached version of the content is still fresh.

Third, make sure the "Content-Length" response header is sent. This probably involves buffering the produced content on the server before it's sent to the client. In ESXX, you do this by setting the "buffered" property on the Response object to "true", like this:

var result = new Response(200, headers, body, content_type);
result.buffered = true;
return result;


The Cacheability Query is a great tool for checking these headers!

With the content now cachable (the browsers are already caching your pages locally), it's time to make sure Apache caches the content locally as well, and serves the cached pages directly from disk (or rather, the OS' in-RAM disk cache).

And that's when it becomes tricky. I tried on both CentOS 4 (Apache 2.0.52) and Fedora 9 (2.2.8), and neither would cache anything but static files! And trust me, I tried hard.

The solution I finally settled for was to hide the "real" site behind a mod_proxy, and then adding the caching directives to the proxy server. In my case, I added (yes, those are dashes, not dots!)

127.0.0.1    www-esxx-org


to /etc/hosts and then used an Apache (mod_cache and mod_disk_cache) configuration that looks something like this:

<Virtualhost *:80>
ServerName www.esxx.org

<Location>
ProxyPass http://www-esxx-org/
ProxyPassReverse http://www-esxx-org/
</Location>

CacheEnable disk /
CacheRoot "/var/cache/mod_proxy"
</Virtualhost>

<Virtualhost localhost:80>
ServerName www-esxx-org

# Real site configuration goes here

</Virtualhost>


As you can see, the "real" site is only accessible from localhost. One final note! Be aware of the fact that in the "real" site configuration, all accesses will appear to come from 127.0.0.1, so any "Allow from" directives will be completely useless.

Monday, August 4, 2008

Dog Food

Time to eat my own dog food.

I have finally converted esxx.org to use ESXX to generate the content, and it turned out more or less the way I expected it to. And not a single URL changed! Unless you knew, you couldn't tell that the pages are dynamically generated using JavaScript and XSLT.

Except for the tiny configuration file and the XSLT stylesheet used, there are a few lines of code that loads and parses the actual page, calculates some HTTP headers required for caching and, for completeness, also handles the "If-Modified-Since" request header.

On my less-than-$1000 server and measured over a gigabit ethernet wire, it's humming along at a little more than 350 requests per second. While far from spectacular, it's definitely reasonable and more than enough for my needs. The major operations involved are:
  1. Perform a directory listing to get file meta-data
  2. Load the file from disk and parse it, including some external entity references, using a DOM XML parser
  3. Run the document through an XSLT 2.0 stylesheet processor and serialize the result
Here is a summary of the benchmark:
[martin@elsa public_html]$ ab -n 1000 -c 30 http://esxx.org/
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking esxx.org (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Finished 1000 requests


Server Software: Apache/2.0.52
Server Hostname: esxx.org
Server Port: 80

Document Path: /
Document Length: 23127 bytes

Concurrency Level: 30
Time taken for tests: 2.695609 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 23394000 bytes
HTML transferred: 23127000 bytes
Requests per second: 370.97 [#/sec] (mean)
Time per request: 80.868 [ms] (mean)
Time per request: 2.696 [ms] (mean, across all concurrent requests)
Transfer rate: 8474.89 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 1
Processing: 12 79 35.7 73 305
Waiting: 11 75 35.1 69 296
Total: 13 79 35.6 73 305

Percentage of the requests served within a certain time (ms)
50% 73
66% 85
75% 94
80% 103
90% 126
95% 147
98% 175
99% 193
100% 305 (longest request)
[martin@elsa public_html]$
But why stop there? I also activated Apache's mod_disk_cache in front of it and that raised the page rate to about 2500 requests/second and lowered the maximum latency to 19 ms. It's always reassuring to know you're Slashdot-ready ...

Edit: Seems like I got bitten by an Apache bug. In 2008. That's rather sad ... Cache disabled.