Source: HttpServer.js

/**
 * @desc 封装express创建服务的方法
 * @author sam
 * @version 0.8.3
 * */

var https = require('https');

var cluster = require('cluster');

var os = require('os');

var util = require('util');

var EventEmitter = require('events').EventEmitter;

var express = require('express');


/**
 * @desc 创建一个http服务程序,包括https和http服务
 * @constructor HttpServer
 * @this {HttpServer}
 * @param {Object} options
 * @example
 *
 *
 *'use strict';
 *
 *var HttpServer = require('epi').HttpServer;
 *
 *var util = require('util');
 *
 * var config = {
 *      host : null,
 *      key : 'string',    //https监听时需要这个key
 *      cert : 'string',   //https监听的时候需要证书
 *      version : '1.0.0'  //api默认的版本信息
 *      port : {
 *          http  : 8000, //如果想要监听http端口,直接写
 *          https : 4433  //如果不想监听https,直接置为null
 *      }
 * }
 *
 * //创建一个httpServer=====================================
 * var httpServer = new HttpServer(config);
 *
 *
 * //设置==================================================
 * httpServer.setting(function (app) {
 *    app.set('trust proxy', true);
 *    app.set('x-powered-by', false);
 * });

 * //导入中间件=============================================
 * var body = require('body-parser');
 * var cookie = require('cookie-parser');
 * var compression = require("compression");
 *
 *  //应用中间件
 * httpServer.use(function (app) {
 *    app.use(compression());
 *    app.use(cookie());
 *    app.use(body.json());
 *    app.use(body.urlencoded({
 *        extended: true
 *    }));
 * });
 *
 *
 * //导入路由==============================================
 * var testRouter = require('./router/test');
 *
 * //注册路由
 * testRouter.map(httpServer);

 * //错误处理=============================================
 * httpServer.error(function (err, req, res, next) {
 *    console.error(err.stack);
 *    res.status(500).json({
 *        error: err
 *    });
 * });
 * */
var HttpServer = function (options) {

    /* *
     * {
     *      host : null,
     *      key : 'string',
     *      cert : 'string',
     *      version : '1.0.0'  //api默认的版本信息
     *      port : {
     *          http  : 8000,
     *          https : 4433
     *      }
     * }
     * */
    options = options || {};

    EventEmitter.call(this);

    this.__host = options.host || null;

    this.__version = options.version;

    this.__key = options.key;

    this.__cert = options.cert;

    this.__port = {
        http: options.port.http,
        https: options.port.https
    };

    this.__server = null;

    /**
     * 如果调用cluster方法启动程序,所有子进程都在这里
     * */
    this.workers = null;

    /**
     * express 创建的app,如果想调用express的相关方法,可以使用这个app
     * */
    this.app = null;

    this.__init();

};

util.inherits(HttpServer, EventEmitter);

/**
 * @desc 初始化系统
 * @access private
 * */
HttpServer.prototype.__init = function () {

    this.app = express();

    if (this.__port.https) {

        if (!this.__key || !this.__cert) {
            throw new Error('listen https need key and cert');
        }

        this.__server = https.createServer({
            key: this.__key,
            cert: this.__cert
        }, this.app);
    }

    /**
     * 用于解析version和device的中间件
     * 添加一个解析version和device设备类型的中间件
     */
    this.app.use(function (req, res, next) {

        req.startTime = Date.now();

        req.version = req.get('accept-version') || req.query.version;

        req.channel = req.get('client-channel') || req.query.channel;

        req.cache = {
            use : req.get('use-api-cache') ? true : false,
            ttl : null,
            check : function(callback){
                callback();
            }
        };

        next();

    });

    return this.app;
};


/***
 * @desc handler
 * */
var routerHandler = function (options, handler, ttl) {

    return function(req, res, next){

        if (options.v && req.version !== options.v) {
            return next();
        }

        if (options.c && req.channel !== options.c) {
            return next();
        }

        if(util.isNumber(cache) && req.cache && (req.cache.use == true) && util.isFunction(req.cache.check)){

            req.cache.check(function(err, result){

                if(!err && result){

                    res.set('data-from-cache', 'true');

                    return res.send(result);

                }else{

                    req.cache.ttl = ttl;

                    handler(req, res, next);

                }
            });
        }else{
            handler(req, res, next);
        }
    }
};


/**
 * @desc Router.all 相当于express router.all
 * @param {String | Regex} path  '/api/name' | /^\/api\/name$/
 * @param {String} options .v '1.0.1'
 * @param {String} options .c  你定义的频道类型
 * @param handler {Function} function(req, res, next){}
 * @param ttl {Number} 设置api缓存时间
 * @example
 * 使用缓存:
 * httpServer.all('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, 100)
 *
 * 不使用
 * httpServer.all('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){})
 * httpServer.all('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, null)
 * */
HttpServer.prototype.all = function (path, options, handler, ttl) {

    if (!util.isObject(options)) {
        throw new Error('options is object {v : "1.0.0", c : "pc"}');
    }

    options = options || {};

    options.v = options.v || this.__version;

    this.app.all(path, routerHandler(options, handler, ttl));
};


/**
 * @desc Router.get 相当于express router.get
 * @param {String | Regex} path  '/api/name' | /^\/api\/name$/
 * @param {String} options .v '1.0.1'
 * @param {String} options .c  你定义的频道类型
 * @param handler {Function} function(req, res, next){}
 * @param ttl {Number} 设置api缓存时间
 * @example
 * 使用缓存:
 * httpServer.get('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, 100)
 *
 * 不使用
 * httpServer.get('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){})
 * httpServer.get('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, null)
 * */
HttpServer.prototype.get = function (path, options, handler, ttl) {

    if (!util.isObject(options)) {
        throw new Error('options is object {v : "1.0.0", c : "pc"}');
    }

    var self = this;

    options = options || {};

    options.v = options.v || self.__version;

    self.app.get(path, routerHandler(options, handler, ttl));
};


/**
 * @desc Router.post 相当于express router.post
 * @param {String | Regex} path  '/api/name' | /^\/api\/name$/
 * @param {String} options .v '1.0.1'
 * @param {String} options .c  你定义的频道类型
 * @param handler {Function} function(req, res, next){}
 * @param ttl {Number} 设置api缓存时间
 * @example
 * 使用缓存:
 * httpServer.post('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, 100)
 *
 * 不使用
 * httpServer.post('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){})
 * httpServer.post('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, null)
 * */
HttpServer.prototype.post = function (path, options, handler, ttl) {

    if (!util.isObject(options)) {
        throw new Error('options is object {v : "1.0.0", c : "pc"}');
    }

    options = options || {};

    options.v = options.v || this.__version;

    this.app.post(path, routerHandler(options, handler, ttl));
};


/**
 * @desc Router.put 相当于express router.put
 * @param {String | Regex} path  '/api/name' | /^\/api\/name$/
 * @param {String} options .v '1.0.1'
 * @param {String} options .c  你定义的频道类型
 * @param handler {Function} function(req, res, next){}
 * @param ttl {Number} 设置api缓存时间
 * @example
 * 使用缓存:
 * httpServer.put('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, 100)
 *
 * 不使用
 * httpServer.put('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){})
 * httpServer.put('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, null)
 * */
HttpServer.prototype.put = function (path, options, handler, ttl) {

    if (!util.isObject(options)) {
        throw new Error('options is object {v : "1.0.0", c : "pc"}');
    }

    options = options || {};

    options.v = options.v || this.__version;

    this.app.put(path, routerHandler(options, handler, ttl));
};


/**
 * @desc Router.delete 相当于express router.delete
 * @param {String | Regex} path  '/api/name' | /^\/api\/name$/
 * @param {String} options .v '1.0.1'
 * @param {String} options .c  你定义的频道类型
 * @param handler {Function} function(req, res, next){}
 * @param ttl {Number} 设置api缓存时间
 * @example
 * 使用缓存:
 * httpServer.delete('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, 100)
 *
 * 不使用
 * httpServer.delete('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){})
 * httpServer.delete('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, null)
 * */
HttpServer.prototype.delete = function (path, options, handler, ttl) {

    if (!util.isObject(options)) {
        throw new Error('options is object {v : "1.0.0", c : "pc"}');
    }

    options = options || {};

    options.v = options.v || this.__version;

    this.app.delete(path, routerHandler(options, handler, ttl));
};


/**
 * @desc Router.header 相当于express router.header
 * @param {String | Regex} path  '/api/name' | /^\/api\/name$/
 * @param {String} options .v '1.0.1'
 * @param {String} options .c  你定义的频道类型
 * @param handler {Function} function(req, res, next){}
 * @param ttl {Number} 设置api缓存时间
 * @example
 * 使用缓存:
 * httpServer.header('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, 100)
 *
 * 不使用
 * httpServer.header('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){})
 * httpServer.header('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, null)
 * */
HttpServer.prototype.header = function (path, options, handler, ttl) {

    if (!util.isObject(options)) {
        throw new Error('options is object {v : "1.0.0", c : "pc"}');
    }

    options = options || {};

    options.v = options.v || this.__version;

    this.app.header(path, routerHandler(options, handler, ttl));
};


/**
 * @desc Router.options 相当于express router.options
 * @param {String | Regex} path  '/api/name' | /^\/api\/name$/
 * @param {String} options .v '1.0.1'
 * @param {String} options .c  你定义的频道类型
 * @param handler {Function} function(req, res, next){}
 * @param ttl {Number} 设置api缓存时间
 * @example
 * 使用缓存:
 * httpServer.options('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, 100)
 *
 * 不使用
 * httpServer.options('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){})
 * httpServer.options('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, null)
 * */
HttpServer.prototype.options = function (path, options, handler, ttl) {

    if (!util.isObject(options)) {
        throw new Error('options is object {v : "1.0.0", c : "pc"}');
    }

    options = options || {};

    options.v = options.v || this.__version;

    this.app.options(path, routerHandler(options, handler, ttl));
};


/**
 * @desc Router.trace 相当于express router.trace
 * @param {String | Regex} path  '/api/name' | /^\/api\/name$/
 * @param {String} options .v '1.0.1'
 * @param {String} options .c  你定义的频道类型
 * @param handler {Function} function(req, res, next){}
 * @param ttl {Number} 设置api缓存时间
 * @example
 * 使用缓存:
 * httpServer.trace('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, 100)
 *
 * 不使用
 * httpServer.trace('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){})
 * httpServer.trace('/test', {v : '1.0.0', c : 'pc'}, function(req, res, next){}, null)
 * */
HttpServer.prototype.trace = function (path, options, handler, ttl) {

    if (!util.isObject(options)) {
        throw new Error('options is object {v : "1.0.0", c : "pc"}');
    }

    options = options || {};

    options.v = options.v || this.__version;

    this.app.trace(path, routerHandler(options, handler, ttl));
};


/**
 * @desc 设置程序属性
 * @param {Function} fn function(app){}
 * @example
 * httpServer.setting(function (app) {
 *   app.set('trust proxy', true);
 *   app.set('x-powered-by', false);
 * });
 * */
HttpServer.prototype.setting = function (fn) {
    fn(this.app);
};

/**
 * @desc 应用中间件
 * @param fn {Function} function(app){}
 * @example
 * httpServer.use(function(app){
 *      app.use(compression());
 *      app.use(timeout('10s'));
 *      app.use(cookie());
 *      app.use(body.json());
 * });
 * */
HttpServer.prototype.use = function (fn) {
    fn(this.app);
};


/**
 * @desc 注册路由
 * @param fn {Function} function(app){}
 * */
HttpServer.prototype.route = function (fn) {
    fn(this.app);
};


/**
 * @desc 添加错误处理
 * @param fn {Function} function(err, req, res, next){}
 * */
HttpServer.prototype.error = function (fn) {
    this.app.use(function (err, req, res, next) {
        fn(err, res, res, next);
    });
};


/**
 * @desc 监听端口
 * @param callback {Function} 监听后返回的消息
 * @example
 * httpServer.listen(8000, function(err, message){
 *      console.log(message);
 * })
 * */
HttpServer.prototype.listen = function (callback) {

    if (!this.__port.http && !this.__port.https) {
        throw new Error('port.http or port.https is not set');
    }

    var listenMessage = '';

    var flag = 0;

    var count = 0;

    var self = this;

    if (this.__port.http) {
        count++;
    }

    if (this.__port.https) {
        count++;
    }

    if (this.__port.http) {

        this.app.listen(this.__port.http, self.__host, function (err) {

            flag++;

            if (err) {
                listenMessage += err.message;
            }

            var host = self.__host || '127.0.0.1';

            listenMessage += 'server listen on : http://' +
                host +
                ':' +
                self.__port.http +
                ', pid : ' +
                process.pid;

            if (flag >= count) {

                self.emit('listen', self.__port.http);

                callback && callback(listenMessage);
            }
        });

    }

    if (this.__port.https) {

        this.__server.listen(this.__port.https, self.__host, function (err) {

            flag++;

            if (err) {
                listenMessage += err.message;
            }

            var host = self.__host || '127.0.0.1';

            listenMessage += '\nserver listen on : https://' +
                host +
                ':' +
                self.__port.https +
                ', pid : ' +
                process.pid;

            if (flag >= count) {

                self.emit('listen', self.__port.https);

                callback && callback(listenMessage);
            }

        });

    }
};


/**
 * @desc 以集群的方式启动程序
 * @param number {Number} 要启动的监听进程数量,null || 0 启动和 cpu个数 - 1 的进程数量
 * @param callback {Function} 监听后返回的消息
 * @example
 *  //子进程启动事件
 *  httpServer.on('childStart', function(worker){
 *       //记录子进程启动
 *       logger.info(Date.now(), worker.pid);
 *  });
 *
 *
 *  //子进程退出事件
 *  httpServer.on('childStop', function(worker, code, signal){
 *      //记录子进程非正常退出
 *      if(!signal && code !== 0){
 *          logger.fatal(Date.now(), worker.pid, code, signal);
 *      }
 *  });
 *
 *  //子进程退出事件
 *  httpServer.on('childRestart', function(worker){
 *      //记录子进程重启
 *      logger.fatal(Date.now(), worker.pid);
 *  });
 *
 *
 *  //使用集群方式监听=======================================
 *  httpServer.cluster(0, function (message) {
 *      console.log(message);
 *  });
 * */
HttpServer.prototype.cluster = function (number, callback) {

    var self = this;

    var cpuCount = number || os.cpus().length - 1;

    self.workers = {};

    if (cluster.isMaster) {

        for (var i = 0; i < cpuCount; i++) {

            var workerProcess = cluster.fork();

            self.workers[workerProcess.process.pid] = workerProcess;

            self.emit('childStart', workerProcess);

        }

        cluster.on('exit', function (worker, code, signal) {

            self.emit('childStop', worker, code, signal);

            delete self.workers[worker.process.pid];

            setTimeout(function(){

                var workProcess = cluster.fork();

                self.workers[workProcess.process.pid] = cluster.fork();

                self.emit('childRestart', workProcess);

            }, 1000);

        });

    } else {

        self.listen(callback);
    }

};

exports.HttpServer = HttpServer;