Skip to content

Instantly share code, notes, and snippets.

@OhMeadhbh
Created February 12, 2015 19:32
Show Gist options
  • Save OhMeadhbh/76581bc39fef78f72eb1 to your computer and use it in GitHub Desktop.
Save OhMeadhbh/76581bc39fef78f72eb1 to your computer and use it in GitHub Desktop.
node.js http.server object lifecycle
// lifecycle.js
//
// This example demonstrates how to start, use and eventually shut down a node.js server. Many
// node tutorials will tell you to completely restart a node process when changing a http
// server's behavior. This is good advice, since it can limit the effect of memory leaks and
// you don't have to worry about cross-version side effects on your business logic. But
// every now and again you'll run across a situation where it's impractical to restart a
// node process to upgrade server behaviour. For instance, we once made an "intelligent"
// proxy that would change it's logging and routing behaviour based on whether it (or we)
// thought an attack was happening.
//
// This example will start a server, using the _version_1_request() function to handle
// requests. After 30 seconds (or receiving the HUP signal) it will change to using the
// _version_2_request() function.
//
// But you'll notice something interesting after the change-over. If you have a web
// browser pointing at the server during the change-over, it may not immediately
// see the update. This is (most likely) because most modern browsers keep connections
// to various servers open for a while after an initial request in case they need to
// send another HTTP request. In node, there is a strong relationship between http.server
// objects and TCP/IP connections. As a result, node http.server objects won't completely
// close if there's still an open TCP/IP socket w/ a HTTP keep-alive request on it.
//
// So what you _should_ see is browsers that make connections before the change-over will
// continue to get the old behaviour until their keep-alive connections time out and they
// are forced to renegotiate a new TCP/IP connection.
//
// This is especially important if you're dealing with a web resource that takes a long
// time to respond to requests. When you change over from one server version to the next,
// you probably don't want to just close pending requests. Rather, you want them to
// complete with the expected behaviour from the old behaviour and new requests get the
// new behaviour.
//
var http = require( 'http' );
var util = require( 'util' );
var port = 9091;
var host;
var timeout_value = 30000;
var changed = false;
// Create a server that exhibits version 1 behaviour
_log( 'SERVER: Creating Version 1 Server' );
var server = http.createServer( _version_1_request );
_log( 'SERVER: Listening on ' + (host?(host):'0.0.0.0') + ':' + port );
server.listen( port, host );
// Setup a signal handler AND a timer to change from v1 to v2 behaviour
_log( 'SERVER: Installing SIGHUP handler' );
process.on( 'SIGHUP', _change_over );
_log( 'SERVER: Setting server change-over timer for ' + timeout_value / 1000 + ' seconds' );
var timer = setTimeout( _change_over, 30000 );
var _version_1_template = '<!DOCTYPE html>' +
'<html>' +
'<head>' +
'<meta http-equiv="refresh" content="5"/>' +
'</head>' +
'<body>' +
'<table style="width: 100%; border: 1px solid black;">' +
'<tr>' +
'<td style="width: 25%">Version</td>' +
'<td>1</td>' +
'</tr>' +
'<tr>' +
'<td>Date</td>' +
'<td>%1</td>' +
'</tr>' +
'</table>' +
'</body>' +
'</html>';
function _version_1_request( request, response ) {
_log( 'REQUEST 1: Received Request: ' + request.url );
var output = new Buffer( _version_1_template.replace( '%1', ( new Date() ).toUTCString() ) );
response.writeHead( 200, {
'content-type': 'text/html',
'content-length': output.length
} );
response.end( output );
}
var _version_2_template = '<!DOCTYPE html>' +
'<html>' +
'<head>' +
'<meta http-equiv="refresh" content="5"/>' +
'</head>' +
'<body>' +
'<table style="width: 100%; border: 1px solid black;">' +
'<tr>' +
'<td style="width: 25%; border: 1px solid black;">Version</td>' +
'<td style="border: 1px solid black;">2</td>' +
'</tr>' +
'<tr>' +
'<td style="border: 1px solid black;">Date</td>' +
'<td style="border: 1px solid black;">%1</td>' +
'</tr>' +
'<tr>' +
'<td style="border: 1px solid black;">URL Path</td>' +
'<td style="border: 1px solid black;">%2</td>' +
'</tr>' +
'</table>' +
'</body>' +
'</html>';
function _version_2_request( request, response ) {
var status, output, type;
_log( 'REQUEST 2: Received Request: ' + request.url );
if( '/favicon.ico' === request.url ) {
status = 404;
output = http.STATUS_CODES[ status ];
type = 'text/plain';
} else {
status = 200;
output = _version_2_template.replace( '%1', ( new Date() ).toUTCString() );
output = output.replace( '%2', request.url );
type = 'text/html';
}
var output_buffer = new Buffer( output );
response.writeHead( status, {
'content-type': type,
'content-length': output_buffer.length
} );
response.end( output_buffer );
}
function _change_over () {
_log( 'SERVER: Received signal to change from Version 1 to Version 2' );
if( changed ) {
_log( "SERVER: But I think I'm already serving Version 2" );
return;
}
changed = true;
if( timer ) {
clearTimeout( timer );
}
// If you call close() on the server object returned from http.createServer, it
// will stop listening. However... sockets that are currently open serving
// keep-alive requests will stay open.
//
// So if you had a web page open looking at this address/port, you'll see the
// old behaviour for some period of time until the browser closes it's
// keep-alive connections.
//
_log( 'SERVER: Sending server the close message' );
server.close( function () {
_log( 'SERVER: I think all instances of version 1 behaviour are closed.' );
} );
_log( 'SERVER: Creating Version 2 Server' );
server = http.createServer( _version_2_request );
_log( 'SERVER: Listening on ' + (host?(host):'0.0.0.0') + ':' + port );
server.listen( port, host );
}
function _log( message ) {
util.log( message );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment