commit d3e8b0efa7db6598194e511a68714dd5d0f7735f Author: 斟酌 鵬兄 Date: Fri Oct 10 16:38:06 2014 +0800 Initial commit diff --git a/Dragonfly.js b/Dragonfly.js new file mode 100644 index 0000000..8ff0f28 --- /dev/null +++ b/Dragonfly.js @@ -0,0 +1,122 @@ +var util = require('util'); + +/*{{{ Private methods */ +// Months +var mon = [ '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12' ]; + +function padZero( num ) +{ + if( num < 10 ) return "0" + String( num ); + return String( num ); +} + +function logDate( date ) +{ + return "[ " + date.getFullYear() + + "-" + mon[ date.getMonth() ] + + "-" + padZero( date.getDate() ) + + " " + padZero( date.getHours() ) + + ":" + padZero( date.getMinutes() ) + + ":" + padZero( date.getSeconds() ) + + " ] "; +} +/*}}}*/ +// Logger +function Dragonfly() +{ + // Static properties + Dragonfly.currentSphere = 10; + + Dragonfly.Spheres = { + THERMO: 30 + , STRATO: 20 + , HYDRO: 10 + , LITHO: 0 + }; + + Dragonfly.Visibility = { + VISIBLE: 9 + , VH8: 8, VH7: 7, VH6: 6 + , HIDDEN: 5 + , HU4: 4, HU3: 3, HU2: 2 + , UNSEEN: 1 + }; + + // Bind prototype functions + for( var i in Dragonfly.prototype ) + { + Dragonfly[i] = this[i].bind( this ); + } + + var cluster = require("cluster"); + this.ptag = "[ " + ( cluster.isMaster ? "M" : "S" ) + ":" + process.pid + " ] "; + + this.Info( "Dragonfly ready.", Dragonfly.Visibility.VISIBLE ); +} + +Dragonfly.prototype.Info = function( mesg, visibility ) +{ + this.Log( mesg, Dragonfly.Spheres.THERMO, visibility ); +}; + +Dragonfly.prototype.Warning = function( mesg, visibility ) +{ + this.Log( mesg, Dragonfly.Spheres.STRATO, visibility ); +}; + +Dragonfly.prototype.Error = function( mesg, visibility ) +{ + this.Log( mesg, Dragonfly.Spheres.HYDRO, visibility ); +}; + +Dragonfly.prototype.Debug = function( mesg, visibility ) +{ + this.Log( mesg, Dragonfly.Spheres.LITHO, visibility ); +}; + +Dragonfly.prototype.Log = function( mesg, sphere, visibility ) +{ + if( isNaN( sphere ) ) sphere = Dragonfly.Spheres.LITHO; + + visibility = Number( visibility ); + isNaN( visibility ) && ( visibility = 0 ); + + sphere += visibility; + + var write = true; + if( Dragonfly.currentSphere < sphere ) + { + write = ( Dragonfly.currentSphere % 10 < sphere % 10 ); + } + + // Writeline if yes + write && this.writeLine( mesg ); +}; + +Dragonfly.prototype.writeLine = function () +{ + for( var i in arguments ) + { + if( typeof( arguments[i] ) == "string" ) + { + this.__log( arguments[i] ); + } + else + { + var lines = util.inspect( arguments[i] ).split("\n"); + for( var j in lines ) + { + this.__log( lines[j] ); + } + } + } +}; + +Dragonfly.prototype.__log = function ( line ) +{ + console.log( this.ptag + logDate( new Date() ) + line ); +}; + +new Dragonfly(); + +global.Dragonfly = Dragonfly; diff --git a/errors/FatalError.js b/errors/FatalError.js new file mode 100644 index 0000000..541954d --- /dev/null +++ b/errors/FatalError.js @@ -0,0 +1,19 @@ +function error( msg ) +{ + this.name = "Fatal Error"; + this.message = msg; + + var e = new Error(); + e = e.stack.split( "\n" ); + e[0] = this.name; + e[1] = ""; + + this.stack = e.join( "\n" ); +} + +error.prototype.toString = function() +{ + return this.name + " " + this.message; +} + +module.exports = error; diff --git a/errors/InternalServerError.js b/errors/InternalServerError.js new file mode 100644 index 0000000..a0faa6c --- /dev/null +++ b/errors/InternalServerError.js @@ -0,0 +1,12 @@ +function error( msg ) +{ + this.name = "Internel Server Error"; + this.message = msg; +} + +error.prototype.toString = function() +{ + return this.name + " " + this.message; +} + +module.exports = error; diff --git a/net/AppDomain.js b/net/AppDomain.js new file mode 100644 index 0000000..9d849ea --- /dev/null +++ b/net/AppDomain.js @@ -0,0 +1,67 @@ +var Dragonfly = global.Dragonfly; + +var domain = require('domain'); +var FatalError = require( '../errors/FatalError' ); + +// Message is hardcoded to prevent further exceptions occured +// This function must be bug-free +function server500( response, e ) +{ + response.statusCode = 500; + response.setHeader( 'Content-Type', 'text/plain' ); + response.end( e.message ); +} + +function serverHandle( server, request, response, rHandle ) +{ + var d = domain.create(); + + d.addListener( 'error', function( e ) { + Dragonfly.Error( e.stack ); + + try + { + var killtimer = setTimeout( function() + { + process.exit(1); + }, 3000); + killtimer.unref(); + + server.close(); + + GLOBAL.X_SERVER_CLUSTER.worker.disconnect(); + + server500( response, e ); + } + catch( ex ) + { + Dragonfly.Error( ex.stack ); + process.exit(); + } + }); + + d.add( request ); + d.add( response ); + + d.run( function() { + rHandle( request, response ); + }); +} + + +// Construncor +function AppDomain( handler, port, cluster ) +{ + var http = require('http'); + var server = http.createServer( + function(req, res) { + serverHandle( server, req, res, handler ); + } + ); + + server.listen( port ); + Dragonfly.Info( "Listening on: " + port, Dragonfly.Visibility.VISIBLE ); +} + + +module.exports = AppDomain; diff --git a/net/Router.js b/net/Router.js new file mode 100644 index 0000000..db51b6e --- /dev/null +++ b/net/Router.js @@ -0,0 +1,111 @@ +var Dragonfly = global.Dragonfly; +var FatalError = require( '../errors/FatalError.js' ); + +var MaxRedirect = 10; + +var stripURI = function( url ) +{ + return url.replace( /\/+/g, '/' ).replace( /^\//, '' ); +}; + +var RelayPoint = function( uri, internal ) +{ + this.direct = Boolean( + typeof internal !== 'undefined' ? internal : false + ); + + this.value = internal ? uri : stripURI( uri ); +}; + +var Router = function( http ) +{ + this.HTTP = http; + this.routes = {}; + this.routable = true; + this.redirect_count = 0; + + // Changed when routing + this.inputs = []; + this.routeObj = {}; + this.reroute = false; + + this.relaying = new RelayPoint( http.request.raw.url ); +}; + +Router.prototype.addRoute = function( name, _route, _action, _status ) +{ + this.routes[ name ] = { + route: _route + , action: String( _action ) + , status: Boolean( + typeof _status !== 'undefined' ? _status : true + ) + }; +}; + +Router.prototype.route = function() +{ + if( MaxRedirect < this.redirect_count ) + throw new FatalError( "Max redirection reached" ); + + this.redirect_count ++; + this.inputs[ this.inputs.length ] = this.relaying; + + var currentRoute = this.relaying.value; + var r; + + if( this.relaying.direct ) + { + if( r = this.routes[ this.relaying.value ] ) + { + this.routable = false; + this.setRoute( r ); + return r.action; + } + } + else + { + for( var i in this.routes ) + { + r = this.routes[i]; + if( r.route.constructor.prototype.exec ) + { + var match = r.route.exec( currentRoute ); + if( match ) + { + // Set routable to false + this.routable = false; + this.setRoute( r, match ); + return r.action; + } + } + } + } + + this.routable = false; + return false; +}; + +// Set Route +Router.prototype.setRoute = function( _route, _match ) +{ + this.routeObj = { + route: _route + , match: _match + , router: this.router + , inputs: this.inputs + + // Re-route function + , reroute: function( target, direct ) + { + Dragonfly.Log( + "Reroute: " + target + + ( direct ? ", Direct" : "" ) + ); + this.relaying = new RelayPoint( target, direct ); + this.routable = true; + this.reroute = true; + }.bind( this ) + }; +}; +module.exports = Router; diff --git a/net/WebFrame.js b/net/WebFrame.js new file mode 100644 index 0000000..5e5c200 --- /dev/null +++ b/net/WebFrame.js @@ -0,0 +1,100 @@ +var Dragonfly = global.Dragonfly; +var FatalError = require( '../errors/FatalError.js' ); + +var Framework = function( garden ) +{ + this.HTTP = garden; + this.result = 0; + this.planted = false; + this.requestStr = ""; + this.requestObj = {}; + + var Router = require( "./Router" ); + this.router = new Router( garden ); + this.router.addRoute( "404", false, "p404" ); + + this.handlers = { + "404": function() + { + this.HTTP.response.statusCode = 404; + this.result = "404 Not Found"; + }.bind( this ) + } +}; + +Framework.prototype.run = function() +{ + var _self = this; + + var method = "GET"; + if( this.HTTP.request.isPost ) + { + this.HTTP.request.raw.addListener( "data", function( data ) + { + _self.requestStr += data.toString(); + }) + this.HTTP.request.raw.addListener( "end", function() + { + _self.parseResult(); + }); + + method = "POST"; + } + + Dragonfly.Info( + method + ": " + encodeURI( this.HTTP.request.raw.url ) + + " - " + this.HTTP.request.raw.headers["user-agent"] + , Dragonfly.Visibility.VISIBLE + ); + + if( method == "GET" ) _self.parseResult(); +}; + +Framework.prototype.addHandler = function( name, method ) +{ + this.handlers[ name ] = method.bind( this ); +}; + +Framework.prototype.parseResult = function() +{ + while( this.router.routable ) + { + var method = this.router.route(); + if( method ) + { + Dragonfly.Debug( "Call " + method ); + + if( this.handlers[ method ] ) + { + this.handlers[ method ]( this.router.routeObj ); + continue; + } + } + + throw new FatalError( "Relay handler \"" + method + "\" is not defined" ); + } + + this.plantResult(); +}; + +Framework.prototype.plantResult = function() +{ + if( !this.planted ) + { + this.planted = true; + if( this.HTTP ) + { + if( !( this.result instanceof Buffer ) ) + { + this.result = String( this.result ); + } + + this.HTTP.response.headers["Content-Length"] = this.result.length; + this.HTTP.response.write( this.result ); + this.HTTP.response.end(); + } + + } +}; + +module.exports = Framework; diff --git a/net/http.js b/net/http.js new file mode 100644 index 0000000..b8f87f4 --- /dev/null +++ b/net/http.js @@ -0,0 +1,41 @@ +var Dragonfly = global.Dragonfly; + +var HTTP = function( req, res ) +{ + var _self = this; + var canExit = true; + + // Simple HTTP Model + this.response = { + statusCode: 200 + , headers: { + 'Content-Type': 'text/html' + } + , write: function( str ) { _self.response.content = str } + , writeLine: function( str ) { _self.response.content += str + "\n"; } + , end: function() + { + if( canExit ) + { + canExit = false; + + var rc = _self.response; + + res.writeHead( rc.statusCode, rc.headers ); + res.end( rc.content ); + } + } + , content: '' + , raw: res + }; + + this.request = { + uri: require('url').parse( req.url ) + , isPost: ( req.method == 'POST' ) + , remoteAddr: req.connection.remoteAddress + , raw: req + }; +}; + + +module.exports = HTTP;