Live Updating a Node.js Web Server

Each time I look at Node.JS the issue of live updating comes up. For PHP it is easy to take live updating for granted. You update your .php files and *poof* your site is running the new code. With the merged web server model of Node.JS that doesn’t happen.

I’m happy to see some folks thinking along these same lines. From Staying up with Node.JS:

To many beginner Node.JS users, a fundamental and immediate apparent disadvantage of writing their web applications with Node.JS lies in the inability to save a file, refresh the browser and see their changes live.

This isn’t just a beginner issue though, it make deploying to production much more complex as well:

The need for seamless code reloads extends into the realm of production deployment as well: one needs to be able to serve new requests with fresh code immediately, without breaking existing ones (such as file uploads or content transfer).

To deal with these issues LearnBoost has developed two projects: distribute and up.

Another option that has been brought up is hotnode, which looks like a fairly simple approach. In general the fewer moving parts the better.

If you are using Node.JS for production services, what approach do you use for deploying updates? Are you doing a load balancer and worker dance (spin up new worker, add it to load balancer pool, drop out an older worker, update it, add it back to the pool) or have you come up with a live update method?

Google’s plusone.js Doesn’t Support HTTP Compression

I was surprised to see that Google’s plusone.js doesn’t support HTTP compression. Here is a quick test with
curl -v --compressed https://apis.google.com/js/plusone.js > /dev/null

Request Headers:

> GET /js/plusone.js HTTP/1.1
> User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8r zlib/1.2.3
> Host: apis.google.com
> Accept: */*
> Accept-Encoding: deflate, gzip

Response Headers:

HTTP/1.1 200 OK
< Content-Type: text/javascript; charset=utf-8
< Expires: Fri, 18 Nov 2011 02:35:20 GMT
< Date: Fri, 18 Nov 2011 02:35:20 GMT
< Cache-Control: private, max-age=3600
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Server: GSE
< Transfer-Encoding: chunked

You'll notice there is no Content-Encoding: gzip header in the response.

We'll have to get Steve Souders to pester them about that.

Cookies That Won’t Die: evercookie

User tracking on the web is an interesting field. There are projects like evercookie that provide insight into the different techniques that are available given todays web client technologies.

The methods listed by evercookie that I thought were particularly curious are:

- Storing cookies in RGB values of auto-generated, force-cached PNGs using HTML5 Canvas tag to read pixels (cookies) back out

- Storing cookies in HTTP ETags

And of course the various methods used by evercookie all cause the original HTTP cookie to re-spawn.

The code is available at https://github.com/samyk/evercookie and is worth a look if you are interested in this sort of thing. The evercookie page has descriptions of how some of the techniques work, along with a sample piece of code to get started with.

Delayed Loading Gravatars

About two months I looked at adding Gravatars to a page, where in most cases there would be 20 different Gravatars on each page view. This seemed like a good time to try out Paul Hammond’s Speed Up Your Site with Delayed Content technique for loading Gravatar images. A few quick hacks later it was working and I was reasonably happy with it.

Fast forward a few more weeks and I found myself wanting to add Gravatars to another page, but this time most of the page views were going involving loading 250 different Gravatar images. I went back to my original script for delayed loading, cleaned it up a bit and applied it to this new page. At this point I realized that this was something that would be nice to have as a simple to use, mostly drop in feature. So I refined it a bit more, made a few more improvements and I’m now making it public as Async Gravatars. The code is open source and available on Github at https://github.com/josephscott/async-gravatars.

The process and the code are actually very simple:

Step 1

Use the same SRC attribute for all of your Gravatar images in the IMG tag, with the md5 of the real Gravatar in the data-gravatar_hash attribute, like so:

<img data-gravatar_hash="713072bbe89035a79c17d19e53dd5d9b"
    class="load-gravatar" height="64" width="64"
    src="https://secure.gravatar.com/avatar/00000000000000000000000000000000?s=64&d=mm" />

For PHP powered sites something like this will do the trick:

<img data-gravatar_hash="<?php echo md5( strtolower( trim( $email ) ) ); ?>"
    class="load-gravatar" height="64" width="64"
    src="https://secure.gravatar.com/avatar/00000000000000000000000000000000?s=64&d=mm" />

It is important to use the same SRC for all of the images, that way your browser only needs to make on request for all of them. And by using a real image with the same dimensions you won’t have issues with the page re-flowing when the real Gravatar gets loaded. I should note that the one SRC value you use doesn’t have to be loaded from Gravatar.com, it can be something that you host on your own site.

Step 2

Load the jQuery code and the async-gravatars.js file, then one small piece of Javascript to trigger loading of the real Gravatar images after the page has finished loading:

jQuery( document ).ready( function() {
    $( '.load-gravatar' ).async_gravatars( {
        ssl: true
    } );
} );

The .async_gravatars() function accepts the following options, with these defaults:

{
		'default_img'	: 'identicon',
		'hash_attr'		: 'data-gravatar_hash',
		'rating'		: 'pg',
		'size'			: 64,
		'ssl'			: false
}

Step 3

Sit back and enjoy, there is no step 3!

Conclusion

If you want to see it in action I put together a little demo page that loads a sample of Gravatar images for some of my fellow Automatticians. A demo page is also included in the Github project.

The Javascript code that does the work is very simple, so spend a minute or two and take a look. If you have suggestions or improvements let me know.

User Agent Sniffing at Google Libraries CDN

I recently took a closer look at Google Libraries, their content delivery network (CDN) for various Javascript libraries, and HTTP compression. I started with a simple test:

curl -O -v --compressed http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js

This downloads a minified version of jQuery 1.4.3, with --compressed, which means I’d like the response to be compressed. The HTTP request looked like:

> GET /ajax/libs/jquery/1.4.3/jquery.min.js HTTP/1.1
> User-Agent: curl/7.16.4 (i386-apple-darwin9.0) libcurl/7.16.4 OpenSSL/0.9.7l zlib/1.2.3
> Host: ajax.googleapis.com
> Accept: */*
> Accept-Encoding: deflate, gzip
> 

The response from Google was:

< HTTP/1.1 200 OK
< Content-Type: text/javascript; charset=UTF-8
< Last-Modified: Fri, 15 Oct 2010 18:25:24 GMT
< Date: Fri, 29 Oct 2010 03:27:16 GMT
< Expires: Sat, 29 Oct 2011 03:27:16 GMT
< Vary: Accept-Encoding
< X-Content-Type-Options: nosniff
< Server: sffe
< Cache-Control: public, max-age=31536000
< Age: 145355
< Transfer-Encoding: chunked
< 

I was surprised that there was no Content-Encoding: gzip header in the response, meaning the response was NOT compressed. I wasn’t quite sure what to make of this at first. No way would Google forget to turn on HTTP compression, I must have missed something. I stared at the HTTP response for sometime, trying to figure out what I was missing. Nothing came to mind, so I ran another test.

This time I made a request for http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js in Firefox 3.6.12 on Mac OS X and used Firebug to inspect the HTTP transaction. The request:

GET /ajax/libs/jquery/1.4.3/jquery.min.js HTTP/1.1
Host: ajax.googleapis.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

and the response:

HTTP/1.1 200 OK
Content-Type: text/javascript; charset=UTF-8
Last-Modified: Fri, 15 Oct 2010 18:25:24 GMT
Date: Fri, 29 Oct 2010 03:12:35 GMT
Expires: Sat, 29 Oct 2011 03:12:35 GMT
Vary: Accept-Encoding
X-Content-Type-Options: nosniff
Server: sffe
Content-Encoding: gzip
Cache-Control: public, max-age=31536000
Content-Length: 26769
Age: 147128

This time the content was compressed. There were several differences in the request headers between curl and Firefox, I decided to start with just one, the “User-Agent”. I modified my initial curl request to include the User-Agent string from Firefox:

curl -O -v --compressed --user-agent "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12" http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js

The request:

> GET /ajax/libs/jquery/1.4.3/jquery.min.js HTTP/1.1
> User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12
> Host: ajax.googleapis.com
> Accept: */*
> Accept-Encoding: deflate, gzip
> 

and the response:

< HTTP/1.1 200 OK
< Content-Type: text/javascript; charset=UTF-8
< Last-Modified: Fri, 15 Oct 2010 18:25:24 GMT
< Date: Fri, 29 Oct 2010 03:33:09 GMT
< Expires: Sat, 29 Oct 2011 03:33:09 GMT
< Vary: Accept-Encoding
< X-Content-Type-Options: nosniff
< Server: sffe
< Content-Encoding: gzip
< Cache-Control: public, max-age=31536000
< Content-Length: 26769
< Age: 147018
< 

Sure enough, I got back a compressed response. Google was sniffing the User-Agent string to determine if a compressed response should be sent. It didn’t matter if the client asked for a compressed response ( Accept-Encoding: deflate, gzip) or not. What still wasn’t clear is if this was a black list approach (singling out curl) or a white list approach (Firefox is okay). So I tried a few other requests with various User-Agent strings. First up, no User-Agent set at all:

curl -O -v --compressed --user-agent "" http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js

Not compressed. Next a made up string:

curl -O -v --compressed --user-agent "JosephScott/1.0 test/2.0" http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js

Not compressed. At this point I think Google is using a white list approach, if you aren’t on the list of approved User-Agent strings for getting a compressed response then you won’t get one, no matter how nicely you ask.

I collected a few more browser samples as well, just to be sure:

  • Safari 5.0.2 on Mac OS X – compressed
  • IE 8 on Windows XP – compressed
  • Firefox 3.6.12 on Windows XP – compressed
  • Chrome 7.0.517.41 beta on Windows XP – compressed
  • Opera 10.63 on Windows XP – NOT compressed
  • Safari 5.0.2 on Windows XP – compressed

One more time, curl using the IE 8 User-Agent string:

curl -O -v --compressed --user-agent "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C;" http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js

Compressed.

Since I can manipulate the response based on the User-Agent value I’m left to conclude that the Google Library CDN sniffs the User-Agent string to determine if it will respond with a compressed result. From what I’ve seen so far Google Library contains a white list of approved User-Agent patterns that it checks against to determine if it will honor the compression request.

If you are on a current version of one of the popular browsers you will get a compressed response. For those using anything else you’ll have to test to confirm if Google Library will honor your request for compressed content. Opera users are just plain out of luck, even the most recent version gets an uncompressed response.