nengine
Version:
The nengine http/https server
346 lines (298 loc) • 8.07 kB
JavaScript
/**
* nengine
* https://nuintun.github.io/nengine
*
* Licensed under the MIT license
* https://github.com/Nuintun/nengine/blob/master/LICENSE
*/
;
// external lib
var os = require('os');
var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var util = require('./util');
var log4js = require('log4js');
var Send = require('file-send');
var pkg = require('../package.json');
var assets = require('../assets');
var cluster = require('cluster');
// variable declaration
var DIRNAME = __dirname;
var NUMCPUS = os.cpus().length;
/**
* file send
* @param requset
* @param response
* @returns {*}
*/
function fileSend(requset, response) {
var context = this;
var options = context.options;
var send = new Send(requset, options);
send.setHeader('Server', 'Nengine/' + pkg.version);
send.setHeader('X-Powered-By', 'Node/' + process.version);
if (options.verbose) {
// error
send.on('headers', function(headers) {
process.send({
type: 'debug',
message: 'Response status:'
+ '\r\nURL : ' + send.url
+ '\r\nPATH : ' + send.path
+ '\r\nROOT : ' + send.root
+ '\r\nREALPATH : ' + send.realpath
+ '\r\nSTATUS : ' + send.statusCode
+ '\r\nHEADERS : ' + JSON.stringify(headers, null, 2)
});
});
}
// dir
send.on('dir', function(fpath, stats, next) {
switch (options.dir) {
case 'allow':
viewFolder.call(context, response, send, fpath, next);
break;
case 'deny':
return send.error(response, 403);
case 'ignore':
default:
return send.error(response, 404);
}
});
// error
send.on('error', function(error, next) {
httpError.call(context, response, send, error, next);
});
// send
return send.pipe(response);
}
/**
* default status
* @param send
* @param error
* @param next
*/
function defaultStatus(send, error, next) {
var context = this;
var statusCode = error.statusCode;
// set Content-Type
send.setHeader('Content-Type', 'text/html; charset=UTF-8');
next(context.assets.html.error(statusCode, error.message));
}
/**
* server error
* @param response
* @param send
* @param error
* @param next
*/
function httpError(response, send, error, next) {
var context = this;
var url = send.url;
var pathname = send.path;
var method = send.method;
var statusCode = error.statusCode;
var errorPage = context.options.error[statusCode];
// logger
process.send({
type: 'warn',
message: method + ': ' + url + ' ' + error.message
});
// not found favicon.ico use default ico
if (statusCode === 404 && pathname === '/favicon.ico' && pathname !== context.favicon) {
// redirect to default favicon
send.redirect(response, context.favicon);
} else {
// custom error page
if (typeof errorPage === 'string') {
fs.exists(path.join(send.root, errorPage), function(exists) {
if (exists) {
send.redirect(response, errorPage);
} else {
defaultStatus.call(context, send, error, next);
}
});
} else {
// default error page
defaultStatus.call(context, send, error, next);
}
}
}
/**
* format to posix path style
* @param path
* @returns {XML|*|string|void}
*/
function posixPath(path) {
return path.replace(/\\/g, '/');
}
/**
* view folder
* @param response
* @param send
* @param fpath
* @param next
*/
function viewFolder(response, send, fpath, next) {
var context = this;
var pathname = send.path;
// set Content-Type
send.setHeader('Content-Type', 'text/html; charset=UTF-8');
// read dir
fs.readdir(fpath, function(error, files) {
if (error) {
send.statError(response, error);
} else {
// ignore matched
if (context.options.ignoreAccess === 'ignore') {
files = files.filter(function(file) {
return !send.isIgnore(pathname + file);
});
}
// response
next(context.assets.html.dir(pathname, files, fpath));
}
});
}
/**
* NengineServer
* @param options
* @returns {*}
* @constructor
*/
function NengineServer(options) {
options = util.extend(true, {}, options);
// format params
options.root = options.root || CWD;
options.port = options.port || 80;
options.error = options.error || {};
options.ignore = options.ignore ? options.ignore : [];
options.dir = util.string(options.dir) ? options.dir.toLowerCase() : 'deny';
options.ignore = Array.isArray(options.ignore) ? options.ignore : [options.ignore];
// get path program relative to root
var pathToRoot = path.relative(options.root, path.dirname(DIRNAME));
// favicon
this.favicon = posixPath(path.join('/', pathToRoot, 'favicon.ico'));
// not default access
if (options.ignore.length) {
options.ignore.push('!' + this.favicon);
options.ignore.push('!(/node_modules/**/nengine/assets/(images|fonts|css))/**/*');
}
// assets
this.assets = assets(options.root);
// options
this.options = options;
// return instance
return this;
}
// set prototype
NengineServer.prototype = {
run: function() {
var context = this;
var options = context.options;
if (cluster.isMaster) {
// make log dir
var logs = path.join(options.root, 'logs/');
if (!fs.existsSync(logs)) {
try {
fs.mkdirSync(logs);
} catch (e) {
// logger
process.send({
type: 'error',
message: 'Please create "Logs" directory under the root directory'
});
// exit process
process.exit();
}
}
// configure log4js
log4js.configure({
appenders: {
out: {
type: 'stdout',
layout: { type: 'coloured' }
},
nengine: {
type: 'dateFile',
filename: logs,
pattern: 'yyyy-MM-dd.txt',
alwaysIncludePattern: true
}
},
categories: {
default: { appenders: ['out', 'nengine'], level: 'ALL' }
}
});
// get logger
var logger = log4js.getLogger('Nengine');
// worker
var worker;
// create thread
for (var i = 0; i < NUMCPUS; i++) {
// fork
worker = cluster.fork();
// listen event
worker.on('message', function(data) {
logger[data.type](data.message);
});
}
} else {
var server;
// choose http type
if (options.key && options.cert) {
server = https;
} else {
server = http;
}
// create server
server = server.createServer(function(requset, response) {
// send file
fileSend.call(context, requset, response);
});
// start listening
server.on('listening', function() {
var address = server.address();
var hostname = options.hostname
? address.address : '127.0.0.1';
// logger
process.send({
type: 'info',
message: 'Server thread ' + cluster.worker.id + ' runing at: ' + hostname + ':' + address.port
});
});
// error
server.on('error', function(error) {
// logger
process.send({
type: 'error',
message: 'Server thread ' + cluster.worker.id + ' failed to start: ' + error.message
});
// exit
process.exit();
});
// close
server.on('close', function() {
// logger
process.send({
type: 'info',
message: 'Server thread ' + cluster.worker.id + ' closed'
});
// exit
process.exit();
});
// listen
if (options.hostname) {
server.listen(options.port, options.hostname);
} else {
server.listen(options.port);
}
// return
return server;
}
}
};
// The module to be exported.
module.exports = NengineServer;