2015-05-28 18:16:09 +03:00
|
|
|
// # Ghost Server
|
|
|
|
// Handles the creation of an HTTP Server for Ghost
|
2014-08-17 10:17:23 +04:00
|
|
|
var Promise = require('bluebird'),
|
2015-05-19 18:51:53 +03:00
|
|
|
chalk = require('chalk'),
|
2014-08-19 20:36:46 +04:00
|
|
|
fs = require('fs'),
|
|
|
|
semver = require('semver'),
|
|
|
|
packageInfo = require('../../package.json'),
|
2014-09-23 19:59:39 +04:00
|
|
|
errors = require('./errors'),
|
2014-08-19 20:36:46 +04:00
|
|
|
config = require('./config');
|
|
|
|
|
2015-05-28 18:16:09 +03:00
|
|
|
/**
|
|
|
|
* ## GhostServer
|
|
|
|
* @constructor
|
|
|
|
* @param {Object} rootApp - parent express instance
|
|
|
|
*/
|
2014-09-19 20:17:58 +04:00
|
|
|
function GhostServer(rootApp) {
|
|
|
|
this.rootApp = rootApp;
|
2014-08-19 20:36:46 +04:00
|
|
|
this.httpServer = null;
|
2014-11-11 09:35:37 +03:00
|
|
|
this.connections = {};
|
|
|
|
this.connectionId = 0;
|
2014-08-19 20:36:46 +04:00
|
|
|
this.upgradeWarning = setTimeout(this.logUpgradeWarning.bind(this), 5000);
|
2014-08-24 00:42:44 +04:00
|
|
|
|
|
|
|
// Expose config module for use externally.
|
|
|
|
this.config = config;
|
2014-08-19 20:36:46 +04:00
|
|
|
}
|
|
|
|
|
2015-05-28 18:16:09 +03:00
|
|
|
/**
|
|
|
|
* ## Public API methods
|
|
|
|
*
|
|
|
|
* ### Start
|
|
|
|
* Starts the ghost server listening on the configured port.
|
|
|
|
* Alternatively you can pass in your own express instance and let Ghost
|
|
|
|
* start listening for you.
|
|
|
|
* @param {Object} externalApp - Optional express app instance.
|
|
|
|
* @return {Promise} Resolves once Ghost has started
|
|
|
|
*/
|
|
|
|
GhostServer.prototype.start = function (externalApp) {
|
|
|
|
var self = this,
|
|
|
|
rootApp = externalApp ? externalApp : self.rootApp;
|
|
|
|
|
|
|
|
return new Promise(function (resolve) {
|
|
|
|
var socketConfig = config.getSocket();
|
|
|
|
|
|
|
|
if (socketConfig) {
|
|
|
|
// Make sure the socket is gone before trying to create another
|
|
|
|
try {
|
|
|
|
fs.unlinkSync(socketConfig.path);
|
|
|
|
} catch (e) {
|
|
|
|
// We can ignore this.
|
|
|
|
}
|
|
|
|
|
|
|
|
self.httpServer = rootApp.listen(socketConfig.path);
|
|
|
|
|
|
|
|
fs.chmod(socketConfig.path, socketConfig.permissions);
|
|
|
|
} else {
|
|
|
|
self.httpServer = rootApp.listen(
|
|
|
|
config.server.port,
|
|
|
|
config.server.host
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.httpServer.on('error', function (error) {
|
|
|
|
if (error.errno === 'EADDRINUSE') {
|
|
|
|
errors.logError(
|
|
|
|
'(EADDRINUSE) Cannot start Ghost.',
|
|
|
|
'Port ' + config.server.port + ' is already in use by another program.',
|
|
|
|
'Is another Ghost instance already running?'
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
errors.logError(
|
|
|
|
'(Code: ' + error.errno + ')',
|
|
|
|
'There was an error starting your server.',
|
|
|
|
'Please use the error code above to search for a solution.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
process.exit(-1);
|
|
|
|
});
|
|
|
|
self.httpServer.on('connection', self.connection.bind(self));
|
|
|
|
self.httpServer.on('listening', function () {
|
|
|
|
self.logStartMessages();
|
|
|
|
clearTimeout(self.upgradeWarning);
|
|
|
|
resolve(self);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ### Stop
|
|
|
|
* Returns a promise that will be fulfilled when the server stops. If the server has not been started,
|
|
|
|
* the promise will be fulfilled immediately
|
|
|
|
* @returns {Promise} Resolves once Ghost has stopped
|
|
|
|
*/
|
|
|
|
GhostServer.prototype.stop = function () {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
return new Promise(function (resolve) {
|
|
|
|
if (self.httpServer === null) {
|
|
|
|
resolve(self);
|
|
|
|
} else {
|
|
|
|
self.httpServer.close(function () {
|
|
|
|
self.httpServer = null;
|
|
|
|
self.logShutdownMessages();
|
|
|
|
resolve(self);
|
|
|
|
});
|
|
|
|
|
|
|
|
self.closeConnections();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ### Restart
|
|
|
|
* Restarts the ghost application
|
|
|
|
* @returns {Promise} Resolves once Ghost has restarted
|
|
|
|
*/
|
|
|
|
GhostServer.prototype.restart = function () {
|
|
|
|
return this.stop().then(this.start.bind(this));
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ### Hammertime
|
|
|
|
* To be called after `stop`
|
|
|
|
*/
|
|
|
|
GhostServer.prototype.hammertime = function () {
|
|
|
|
console.log(chalk.green('Can\'t touch this'));
|
|
|
|
|
|
|
|
return Promise.resolve(this);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ## Private (internal) methods
|
|
|
|
*
|
|
|
|
* ### Connection
|
|
|
|
* @param {Object} socket
|
|
|
|
*/
|
2014-08-19 20:36:46 +04:00
|
|
|
GhostServer.prototype.connection = function (socket) {
|
2014-11-11 09:35:37 +03:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
self.connectionId += 1;
|
|
|
|
socket._ghostId = self.connectionId;
|
|
|
|
|
|
|
|
socket.on('close', function () {
|
|
|
|
delete self.connections[this._ghostId];
|
|
|
|
});
|
|
|
|
|
|
|
|
self.connections[socket._ghostId] = socket;
|
2014-08-19 20:36:46 +04:00
|
|
|
};
|
|
|
|
|
2015-05-28 18:16:09 +03:00
|
|
|
/**
|
|
|
|
* ### Close Connections
|
|
|
|
* Most browsers keep a persistent connection open to the server, which prevents the close callback of
|
|
|
|
* httpServer from returning. We need to destroy all connections manually.
|
|
|
|
*/
|
2014-08-19 20:36:46 +04:00
|
|
|
GhostServer.prototype.closeConnections = function () {
|
2014-11-11 09:35:37 +03:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
Object.keys(self.connections).forEach(function (socketId) {
|
|
|
|
var socket = self.connections[socketId];
|
|
|
|
|
|
|
|
if (socket) {
|
|
|
|
socket.destroy();
|
|
|
|
}
|
2014-08-19 20:36:46 +04:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-05-28 18:16:09 +03:00
|
|
|
/**
|
|
|
|
* ### Log Start Messages
|
|
|
|
*/
|
2014-08-19 20:36:46 +04:00
|
|
|
GhostServer.prototype.logStartMessages = function () {
|
|
|
|
// Tell users if their node version is not supported, and exit
|
2015-02-04 00:44:59 +03:00
|
|
|
if (!semver.satisfies(process.versions.node, packageInfo.engines.node) &&
|
|
|
|
!semver.satisfies(process.versions.node, packageInfo.engines.iojs)) {
|
2014-08-19 20:36:46 +04:00
|
|
|
console.log(
|
2015-05-19 18:51:53 +03:00
|
|
|
chalk.red('\nERROR: Unsupported version of Node'),
|
|
|
|
chalk.red('\nGhost needs Node version'),
|
|
|
|
chalk.yellow(packageInfo.engines.node),
|
|
|
|
chalk.red('you are using version'),
|
|
|
|
chalk.yellow(process.versions.node),
|
|
|
|
chalk.green('\nPlease go to http://nodejs.org to get a supported version')
|
2014-08-19 20:36:46 +04:00
|
|
|
);
|
|
|
|
|
|
|
|
process.exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Startup & Shutdown messages
|
|
|
|
if (process.env.NODE_ENV === 'production') {
|
|
|
|
console.log(
|
2015-05-19 18:51:53 +03:00
|
|
|
chalk.green('Ghost is running...'),
|
2014-09-10 08:06:24 +04:00
|
|
|
'\nYour blog is now available on',
|
2014-08-19 20:36:46 +04:00
|
|
|
config.url,
|
2015-05-19 18:51:53 +03:00
|
|
|
chalk.gray('\nCtrl+C to shut down')
|
2014-08-19 20:36:46 +04:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
console.log(
|
2015-05-19 18:51:53 +03:00
|
|
|
chalk.green('Ghost is running in ' + process.env.NODE_ENV + '...'),
|
2014-09-10 08:06:24 +04:00
|
|
|
'\nListening on',
|
2014-08-19 20:36:46 +04:00
|
|
|
config.getSocket() || config.server.host + ':' + config.server.port,
|
2014-09-10 08:06:24 +04:00
|
|
|
'\nUrl configured as:',
|
2014-08-19 20:36:46 +04:00
|
|
|
config.url,
|
2015-05-19 18:51:53 +03:00
|
|
|
chalk.gray('\nCtrl+C to shut down')
|
2014-08-19 20:36:46 +04:00
|
|
|
);
|
2014-11-05 15:20:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function shutdown() {
|
2015-05-19 18:51:53 +03:00
|
|
|
console.log(chalk.red('\nGhost has shut down'));
|
2014-11-05 15:20:10 +03:00
|
|
|
if (process.env.NODE_ENV === 'production') {
|
|
|
|
console.log(
|
|
|
|
'\nYour blog is now offline'
|
|
|
|
);
|
|
|
|
} else {
|
2014-08-19 20:36:46 +04:00
|
|
|
console.log(
|
2014-09-10 08:06:24 +04:00
|
|
|
'\nGhost was running for',
|
2014-08-19 20:36:46 +04:00
|
|
|
Math.round(process.uptime()),
|
2014-09-10 08:06:24 +04:00
|
|
|
'seconds'
|
2014-08-19 20:36:46 +04:00
|
|
|
);
|
2014-11-05 15:20:10 +03:00
|
|
|
}
|
|
|
|
process.exit(0);
|
2014-08-19 20:36:46 +04:00
|
|
|
}
|
2014-11-05 15:20:10 +03:00
|
|
|
// ensure that Ghost exits correctly on Ctrl+C and SIGTERM
|
|
|
|
process.
|
|
|
|
removeAllListeners('SIGINT').on('SIGINT', shutdown).
|
|
|
|
removeAllListeners('SIGTERM').on('SIGTERM', shutdown);
|
2014-08-19 20:36:46 +04:00
|
|
|
};
|
|
|
|
|
2015-05-28 18:16:09 +03:00
|
|
|
/**
|
|
|
|
* ### Log Shutdown Messages
|
|
|
|
*/
|
2014-08-19 20:36:46 +04:00
|
|
|
GhostServer.prototype.logShutdownMessages = function () {
|
2015-05-19 18:51:53 +03:00
|
|
|
console.log(chalk.red('Ghost is closing connections'));
|
2014-08-19 20:36:46 +04:00
|
|
|
};
|
|
|
|
|
2015-05-28 18:16:09 +03:00
|
|
|
/**
|
|
|
|
* ### Log Upgrade Warning
|
|
|
|
* Warning that the API for the node module version of Ghost changed in 0.5.2
|
|
|
|
*
|
|
|
|
* *This should be removed soon*
|
|
|
|
*/
|
2014-08-19 20:36:46 +04:00
|
|
|
GhostServer.prototype.logUpgradeWarning = function () {
|
2014-09-23 19:59:39 +04:00
|
|
|
errors.logWarn(
|
|
|
|
'Ghost no longer starts automatically when using it as an npm module.',
|
|
|
|
'If you\'re seeing this message, you may need to update your custom code.',
|
|
|
|
'Please see the docs at http://tinyurl.com/npm-upgrade for more information.'
|
|
|
|
);
|
2014-08-19 20:36:46 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = GhostServer;
|