thinkjs/src/core/app.js

275 lines
7.6 KiB
JavaScript

'use strict';
import cluster from 'cluster';
import fs from 'fs';
import domain from 'domain';
import os from 'os';
import http from 'http';
export default class extends think.http.base {
/**
* exec logic
* @return {Promise} []
*/
execLogic(){
let name = `${this.http.module}/${think.dirname.logic}/${this.http.controller}`;
let cls = think.require(name, true);
if (!cls) {
return Promise.resolve();
}
let instance = new cls(this.http);
let action = think.camelCase(this.http.action);
if (think.isFunction(instance[`${action}Action`])) {
return this.action(instance, action, false);
}
//call action
else if (think.isFunction(instance.__call)) {
return this.action(instance, '__call', false);
}
//only has before method
else if(think.isFunction(instance.__before)){
return think.co(instance.__before(instance));
//return think.co.wrap(instance.__before).bind(instance)(instance);
}
return Promise.resolve();
}
/**
* exec controller
* @return {Promise} []
*/
execController(){
let http = this.http;
let name = `${http.module}/${think.dirname.controller}/${http.controller}`;
let cls = think.require(name, true);
if (cls) {
return this.execAction(new cls(http));
}
http.error = new Error(think.locale('CONTROLLER_NOT_FOUND', http.controller, http.url));
return think.statusAction(404, http);
}
/**
* exec action
* @param {Object} controller [controller instance]
* @param {Boolean} call [is call controller]
* @return {Promise} []
*/
execAction(controller){
let http = this.http;
//if is rest api, rewrite action
if(controller._isRest){
let method = controller._method;
//get method from GET params
if(method){
method = controller.get(method).toLowerCase();
}
if(!method){
method = http.method.toLowerCase();
}
http.action = method;
}
let action = think.camelCase(http.action);
let actionWithSuffix = `${action}Action`;
//action is exist
if(think.isFunction(controller[actionWithSuffix])){
return this.action(controller, action, false);
}
//call action
if(think.isFunction(controller.__call)){
return this.action(controller, '__call', false);
}
http.error = new Error(think.locale('ACTION_NOT_FOUND', actionWithSuffix, http.url));
return think.statusAction(404, http);
}
/**
* exec
* @return {Promise} []
*/
async exec(){
await this.hook('resource');
await this.hook('route_parse');
//set module config, can not set config in request
this.http._config = think.getModuleConfig(this.http.module);
//babel compile error
if(think.compileError){
this.http.error = think.compileError;
return think.statusAction(500, this.http);
}
await this.hook('logic_before');
await this.execLogic().catch(err => {
//ignore prevent reject promise
//make logic_after hook can be invoked
if(!think.isPrevent(err)){
return Promise.reject(err);
}
});
await this.hook('logic_after');
//http is end
if (this.http._isEnd) {
return think.prevent();
}
await this.hook('controller_before');
await this.execController().catch(err => {
//ignore prevent reject promise
//make controller_after & response_end hook can be invoked
if(!think.isPrevent(err)){
return Promise.reject(err);
}
});
await this.hook('controller_after');
await this.hook('response_end');
}
/**
* run
* @return {} []
*/
run(){
let http = this.http;
http.header('X-Powered-By', `thinkjs-${think.version}`);
//service off
if(!think.config('service_on')){
http.error = new Error(think.locale('SERVICE_UNAVAILABLE'));
return think.statusAction(503, http);
}
//deny access by ip + port
if (think.config('proxy_on') && http.host !== http.hostname && !http.socket) {
http.error = new Error(think.locale('DISALLOW_PORT'));
return think.statusAction(403, http);
}
let instance = domain.create();
instance.on('error', err => {
http.error = err;
think.statusAction(500, http, true);
});
instance.run(async () => {
try{
await this.exec();
}catch(err){
http.error = err;
think.statusAction(500, http, true);
}
});
}
/**
* create server
* @return {} []
*/
static createServer(){
let handle = think.config('create_server');
let host = think.config('host');
let port = think.port || think.config('port');
//createServer callback
let callback = async (req, res) => {
let http = await think.http(req, res);
return new this(http).run();
};
let server;
//define createServer in application
if (handle) {
server = handle(callback, port, host, this);
}else{
//create server
server = http.createServer(callback);
server.listen(port, host);
}
this.logPid(port);
//start websocket
let websocket = think.parseConfig(think.config('websocket'));
if(websocket.on){
let Cls = think.adapter('websocket', websocket.type);
let instance = new Cls(server, websocket, this);
instance.run();
}
}
/**
* log
* @return {} []
*/
static log(){
let host = think.config('host');
let port = think.port || think.config('port');
let websocketStatus = think.config('websocket.on') ? 'open' : 'closed';
let clusterStatus = think.config('cluster_on') ? 'open' : 'closed';
think.log('Server running at http://' + (host || '127.0.0.1') + ':' + port + '/', 'THINK');
think.log(() => `ThinkJS Version: ${think.version}`, 'THINK');
think.log(colors => `Cluster Status: ${colors.magenta(clusterStatus)}`, 'THINK');
think.log(colors => `WebSocket Status: ${colors.magenta(websocketStatus)}`, 'THINK');
think.log(colors => `File Auto Compile: ${colors.magenta(!!think.autoCompile)}`, 'THINK');
think.log(colors => `File Auto Reload: ${colors.magenta(think.config('auto_reload'))}`, 'THINK');
think.log(colors => `App Enviroment: ${colors.magenta(think.env)}\n`, 'THINK');
}
/**
* cli mode
* @return {} []
*/
static async cli(){
let http = await think.http(think.cli);
return new this(http).run();
}
/**
* load process id
* @return {} []
*/
static logPid(port){
if (!cluster.isMaster || !think.config('log_pid')) {
return;
}
let dir = think.getPath(undefined, think.dirname.runtime) + '/pid';
think.mkdir(dir);
let pidFile = `${dir}/${port}.pid`;
fs.writeFileSync(pidFile, process.pid);
//change pid file mode
think.chmod(pidFile);
//remove pid file when process exit
process.on('SIGTERM', () => {
if (fs.existsSync(pidFile)) {
fs.unlinkSync(pidFile);
}
process.exit(0);
});
}
/**
* http mode
* @return {} []
*/
static http(){
let nums = think.config('cluster_on');
if (!nums) {
this.createServer();
return this.log();
}
if (nums === true) {
nums = os.cpus().length;
}
if (cluster.isMaster) {
for (let i = 0; i < nums; i++) {
cluster.fork();
}
cluster.on('exit', worker => {
think.log(new Error(think.locale('WORKER_DIED', worker.process.pid)), 'THINK');
process.nextTick(() => cluster.fork());
});
this.log();
}else {
this.createServer();
}
}
/**
* run
* @return {} []
*/
static run(){
if (think.cli) {
return this.cli();
}
return this.http();
}
}