Ghost/core/server/ghost-server.js

214 lines
5.9 KiB
JavaScript
Raw Normal View History

// # Ghost Server
// Handles the creation of an HTTP Server for Ghost
var Promise = require('bluebird'),
chalk = require('chalk'),
fs = require('fs'),
errors = require('./errors'),
config = require('./config');
/**
* ## GhostServer
* @constructor
* @param {Object} rootApp - parent express instance
*/
function GhostServer(rootApp) {
this.rootApp = rootApp;
this.httpServer = null;
this.connections = {};
this.connectionId = 0;
// Expose config module for use externally.
this.config = config;
}
/**
* ## 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();
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
*/
GhostServer.prototype.connection = function (socket) {
var self = this;
self.connectionId += 1;
socket._ghostId = self.connectionId;
socket.on('close', function () {
delete self.connections[this._ghostId];
});
self.connections[socket._ghostId] = socket;
};
/**
* ### 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.
*/
GhostServer.prototype.closeConnections = function () {
var self = this;
Object.keys(self.connections).forEach(function (socketId) {
var socket = self.connections[socketId];
if (socket) {
socket.destroy();
}
});
};
/**
* ### Log Start Messages
*/
GhostServer.prototype.logStartMessages = function () {
// Startup & Shutdown messages
if (process.env.NODE_ENV === 'production') {
console.log(
chalk.green('Ghost is running...'),
'\nYour blog is now available on',
config.url,
chalk.gray('\nCtrl+C to shut down')
);
} else {
console.log(
chalk.green('Ghost is running in ' + process.env.NODE_ENV + '...'),
'\nListening on',
config.getSocket() || config.server.host + ':' + config.server.port,
'\nUrl configured as:',
config.url,
chalk.gray('\nCtrl+C to shut down')
);
}
function shutdown() {
console.log(chalk.red('\nGhost has shut down'));
if (process.env.NODE_ENV === 'production') {
console.log(
'\nYour blog is now offline'
);
} else {
console.log(
'\nGhost was running for',
Math.round(process.uptime()),
'seconds'
);
}
process.exit(0);
}
// ensure that Ghost exits correctly on Ctrl+C and SIGTERM
process.
removeAllListeners('SIGINT').on('SIGINT', shutdown).
removeAllListeners('SIGTERM').on('SIGTERM', shutdown);
};
/**
* ### Log Shutdown Messages
*/
GhostServer.prototype.logShutdownMessages = function () {
console.log(chalk.red('Ghost is closing connections'));
};
module.exports = GhostServer;