Created
February 12, 2015 19:32
-
-
Save OhMeadhbh/76581bc39fef78f72eb1 to your computer and use it in GitHub Desktop.
node.js http.server object lifecycle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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