Last active
August 29, 2015 14:03
-
-
Save hugoware/6a77042a11c85341bbf1 to your computer and use it in GitHub Desktop.
Lightweight HTTP server for Node for front end development
This file contains 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
var $ = module.exports | |
, $fs = require('fs') | |
, $path = require('path') | |
, $http = require('http') | |
, $query = require('querystring') | |
, $run = require('child_process').spawn | |
// optional jade templates | |
, $jade | |
// track our own simple list | |
, $mime_types = { | |
'.txt' : 'text/plain', | |
'.html' : 'text/html', | |
'.css' : 'text/css', | |
'.js' : 'text/javascript', | |
'.json' : 'application/json', | |
'.png' : 'image/png', | |
'.gif' : 'image/gif', | |
'.jpg' : 'image/jpeg', | |
'.jpeg' : 'image/jpeg' | |
} | |
// list of handlers to use | |
, $resources | |
, $handlers = [ ]; | |
// gets the request ready to use | |
function _prepare_request( request, response ) { | |
request.post = /post/i.test( request.method ); | |
request.get = !request.post; | |
// get request just pass through | |
if ( !request.post ) return _handle_request( request, response ); | |
// prepare data | |
var body = ''; | |
request.on('data', function( data ) { body += data; }); | |
request.on('end', function() { | |
// and add it to the request | |
request.data = $query.parse( body ); | |
_handle_request( request, response ); | |
}); | |
} | |
// sets the public resources, files, etc directory | |
function _setup_public_directory() { | |
var directory = $.config.public || './public' | |
, root = $path.resolve( __dirname, directory ); | |
// handle file requests | |
_add_handler({ route: /^(.*$)/, resources: true, root: root, | |
handler: function( request, response ) { | |
var location = $path.resolve( $path.join( root, request.url )); | |
response.file( location ); | |
}}); | |
} | |
// handles sass requests | |
function _setup_sass_generator( path ) { | |
path = $path.resolve( __dirname, path ); | |
// renders CSS | |
function _generate() { | |
_generate.css = ''; | |
// copy all css | |
var render = $run('sass', [ '--compass', path ]) | |
render.stdout.on('data', function( data ) { | |
_generate.css += data.toString(); | |
}); | |
} | |
// watch for the sass file to update | |
$fs.watchFile( path, _generate ); | |
// render the first pass | |
_generate(); | |
// gets back the most recent css render | |
return function( request, response ) { | |
var css = _generate.css || ''; | |
response.writeHead( 200, { 'Content-Type': $mime_types['.css'], 'Content-Length': css.length }); | |
response.end( css ); | |
} | |
} | |
// render a jade template | |
function _as_jade( response, path, data ) { | |
// load jade now if it's used | |
$jade = $jade || require('jade'); | |
// update the path | |
path = ( $.config.views || './views/' ) + path; | |
if ( !/\.jade$/.test( path )) path += '.jade'; | |
path = $path.resolve( __dirname, path ) | |
// check for the file | |
_use_file( path, response, function() { | |
var compile = $jade.compileFile( path, { pretty: $.config.debug }) | |
, type = $mime_types['.html'] | |
, html; | |
// try and generate the html | |
try { html = compile( data ); } | |
// log error messages | |
catch( err ) { | |
type = $mime_types['.txt']; | |
html = err.toString(); | |
} | |
// write out the result | |
response.writeHead( 200, { 'Content-Type': type, 'Content-Length': html.length }); | |
response.end( html ); | |
}); | |
} | |
// simple redirect | |
function _as_redirect( response, url ) { | |
response.writeHead( 302, { 'Location': url }); | |
response.end(); | |
} | |
// tries to grab a file and overrides with a 404 if it doesn't exist | |
function _use_file( path, response, callback ) { | |
$fs.exists( path, function( exists ) { | |
if ( !exists ) return _as_404( response ); | |
callback( response, path ) | |
}); | |
} | |
// nothing was found | |
function _as_404( response ) { | |
// custom 404 handling | |
if ( $.missing.handle ) | |
return $.missing.handle( response.request, response ); | |
// default handling | |
response.writeHead( 404, { 'Content-Type': $mime_types['.txt'] }); | |
response.end('not found'); | |
} | |
// reads and returns file content | |
function _as_file( response, path ) { | |
_use_file( path, response, function() { | |
// get the content | |
var ext = $path.extname( path ).toLowerCase() | |
, type = $mime_types[ ext ] || 'text/plain'; | |
// read the file | |
$fs.readFile( path, function( err, contents ) { | |
if ( err ) return _as_404( response ); | |
response.writeHead( 200,{ 'Content-Type': type, 'Content-Length': contents.length }); | |
response.end( contents ); | |
}); | |
}); | |
} | |
// sends a javascript object as data | |
function _as_json( response, data ) { | |
var content = JSON.stringify( data ); | |
response.writeHead( 200,{ 'Content-Type': 'text/json', 'Content-Length': content.length }); | |
response.end( content ); | |
} | |
// handles the actual request | |
function _handle_request( request, response ) { | |
console.log('['+ ( request.post ? 'POST' : 'GET' ) +']', request.url ); | |
// add some helpers | |
response.redirect = function( url ) { _as_redirect( response, url ); }; | |
response.json = function( data ) { _as_json( response, data ); }; | |
response.file = function( path ) { _as_file( response, path ); }; | |
response.render = function( path, data ) { _as_jade( response, path, data ); }; | |
// shorcut to return a resource | |
response.resource = function( path ) { | |
if ( !$resources ) throw 'no resource directory'; | |
path = $path.resolve( $path.join( $resources.root, path )); | |
_as_file( response, path ); | |
}; | |
// check if this matches anything | |
var use; | |
for ( var i in $handlers ) { | |
var handler = $handlers[ i ] | |
, method = request.post && handler.post || request.get && handler.get | |
, route = method && handler.route( request.url ) | |
, matches = method && route; | |
// use the handler if it works | |
if ( !matches ) continue; | |
// save handler tp use | |
use = handler.handler; | |
break; | |
} | |
// if it doesn't match, run the resources | |
if ( !use && $resources ) | |
use = $resources.handler; | |
// if still nothing, just end | |
if ( use ) use( request, response ); | |
else _as_404( response ); | |
} | |
// includes a new resource handler | |
function _add_handler( params ) { | |
var route = params.route; | |
// simple string matching | |
if ( typeof route === 'string' ) | |
params.route = function( url ) { | |
return (url+'').toLowerCase() == route; | |
}; | |
// using a regular expression | |
else if ( route instanceof RegExp ) | |
params.route = function( url ) { | |
return (url+'').toLowerCase().match( route ); | |
}; | |
// save the location | |
if ( params.resources ) $resources = params; | |
else $handlers.push( params ); | |
} | |
// additional custom mime types | |
$.type = function( ext, type ) { | |
$mime_types[ ext ] = type; | |
return $; | |
}; | |
// start listening for requests | |
$.listen = function( port ) { | |
// use configs to setup other info | |
_setup_public_directory(); | |
// set up the server | |
var server = $http.createServer( _prepare_request ); | |
server.listen( port ); | |
return $; | |
}; | |
// handles gets | |
$.get = function( route, handle ) { | |
_add_handler({ route: route, get: true, handler: handle }); | |
return $; | |
}; | |
// handles posts | |
$.post = function( route, handle ) { | |
_add_handler({ route: route, post: true, handler: handle }); | |
return $; | |
}; | |
// handles posts and gets | |
$.all = function( route, handle ) { | |
_add_handler({ route: route, get: true, post: true, handler: handle }); | |
return $; | |
}; | |
// if this is a 404 | |
$.missing = function( handle ) { | |
$.missing.handle = handle; | |
return $; | |
}; | |
// maps a route to a resource | |
$.resource = function( route, path ) { | |
return $.all( route, function( request, response ) { | |
response.resource( path ); | |
}); | |
}; | |
// maps a route to a file | |
$.file = function( route, path ) { | |
return $.all( route, function( request, response ) { | |
response.file( path ); | |
}); | |
}; | |
// sets a configuration value | |
$.config = function( key, value ) { | |
$.config[ key ] = value; | |
}; | |
// renders sass and | |
$.sass = function( route, path ) { | |
_add_handler({ route: route, get: true, post: true, | |
handler: _setup_sass_generator( path ) }); | |
return $; | |
}; | |
// shortcuts to create handlers | |
$.as = function( command ) { | |
var args = [].slice.call( arguments, 1 ); | |
return function( req, res ) { res[ command ].apply( res, args ); }; | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment