Repository URL to install this package:
|
Version:
1.7.0-16898 ▾
|
/*
* Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4,
maxerr: 50, node: true */
/*global */
(function () {
"use strict";
/** @define{number} Number of ms to wait for the server to start */
var SETUP_TIMEOUT = 5000; // wait up to 5 seconds for server to start
/** @define{number} Number of ms between pings to parent process */
var PING_DELAY = 1000; // send ping to parent process every 1 second
var fs = require("fs"),
http = require("http"),
WebSocket = require("./thirdparty/ws"),
EventEmitter = require("events").EventEmitter,
Logger = require("./Logger"),
ConnectionManager = require("./ConnectionManager"),
DomainManager = require("./DomainManager");
/**
* @constructor
* The Server module is a singleton object that manages both the
* connection to the parent process (over stdin/stdout) and to clients
* over WebSockets.
*
* Server inherits from the EventEmitter class and exports itself
* as the module.
*/
var Server = module.exports = new EventEmitter();
/**
* @private
* @type{number} unique IDs for messages to parent process
*/
var _commandCount = 1;
/**
* @private
* @type{http.Server} the HTTP server
*/
var _httpServer = null;
/**
* @private
* @type{ws.WebSocketServer} the WebSocket server
*/
var _wsServer = null;
/**
* Stops the server and does appropriate cleanup.
* Emits an "end" event when shutdown is complete.
*/
function stop() {
Logger.info("[Server] stopping");
if (_wsServer) {
try {
_wsServer.close();
} catch (err1) { }
}
if (_httpServer) {
try {
_httpServer.close();
} catch (err2) { }
}
ConnectionManager.closeAllConnections();
Logger.info("[Server] stopped");
Server.emit("end");
Server.removeAllListeners();
}
/**
* Starts the server.
*/
function start() {
function sendCommandToParentProcess() {
var cmd = "\n\n" + (_commandCount++) + "|"
+ Array.prototype.join.call(arguments, "|") + "\n\n";
process.stdout.write(cmd);
}
function httpRequestHandler(req, res) {
if (req.method === "GET") {
if (req.url === "/api" || req.url.indexOf("/api/") === 0) {
res.setHeader("Content-Type", "application/json");
res.end(
JSON.stringify(DomainManager.getDomainDescriptions(),
null,
4)
);
} else {
res.setHeader("Content-Type", "text/plain");
res.end("Brackets-Shell Server\n");
}
} else { // Not a GET request
res.statusCode = 501;
res.end();
}
}
function setupStdin() {
// re-enable getting events from stdin
try {
process.stdin.resume();
process.stdin.setEncoding("utf8");
} catch (e) {
// Couldn't resume stdin, so something is terribly wrong
Logger.error("[Server] unable to resume stdin, stopping");
stop();
}
// set up event handlers for stdin
process.stdin.on("data", function (data) {
// no-op, but make sure we read the data so the buffer
// doesn't fill up
});
process.stdin.on("end", function receiveStdInClose() {
Logger.info("[Server] stopping because stdin closed");
stop();
});
}
function setupStdout() {
// Routinely check if stdout is closed. Stdout will close when our
// parent process closes (either expectedly or unexpectedly) so this
// is our signal to shutdown to prevent process abandonment.
//
// We need to continually ping because that's the only way to actually
// check if the pipe is closed in a robust way (writable may only get
// set to false after trying to write a ping to a closed pipe).
setInterval(function () {
if (!process.stdout.writable) {
// If stdout closes, our parent process has terminated or
// has explicitly closed it. Either way, we should exit.
Logger.info("[Server] stopping because stdout closed");
stop();
} else {
try {
sendCommandToParentProcess("ping");
} catch (e) {
Logger.info("[Server] stopping because stdout was not writable");
stop();
}
}
}, PING_DELAY);
}
function setupHttpAndWebSocketServers(callback, timeout) {
var timeoutTimer = null;
var httpServer = null;
if (timeout) {
timeoutTimer = setTimeout(function () {
callback("ERR_TIMEOUT", null);
}, timeout);
}
httpServer = http.createServer(httpRequestHandler);
httpServer.on("error", function () {
if (callback) {
callback("ERR_CREATE_SERVER", null);
}
});
httpServer.listen(0, "127.0.0.1", function () {
var wsServer = null;
var address = httpServer.address();
if (address !== null) {
httpServer.removeAllListeners("error");
httpServer.on("error", function () {
Logger.error("[Server] stopping due to HTTP error",
arguments);
stop();
});
wsServer = new WebSocket.Server({server: httpServer});
wsServer.on("error", function () {
Logger.error(
"[Server] stopping due to WebSocket error",
arguments
);
stop();
});
wsServer.on("connection",
ConnectionManager.createConnection);
if (timeoutTimer) {
clearTimeout(timeoutTimer);
}
callback(null, {httpServer : httpServer,
wsServer : wsServer,
port : address.port
});
} else {
// address is null
// This shouldn't happen, because if we didn't get a socket
// we wouldn't have called this callback
if (timeoutTimer) {
clearTimeout(timeoutTimer);
}
if (callback) {
callback("ERR_UNKNOWN", null);
}
}
});
}
// Do initialization
Logger.info("[Server] beginning startup");
setupStdin();
setupStdout();
setupHttpAndWebSocketServers(function (err, servers) {
if (err) {
Logger.error(
"[Server] stopping due to error while starting http/ws servers: "
+ err
);
stop();
} else {
Logger.info("[Server] serving on port", servers.port);
_httpServer = servers.httpServer;
_wsServer = servers.wsServer;
// tell the parent process what port we're on
sendCommandToParentProcess("port", servers.port);
}
}, SETUP_TIMEOUT);
DomainManager.loadDomainModulesFromPaths(["./BaseDomain"]);
Logger.info("[Server] startup complete");
}
// Public interface
Server.start = start;
Server.stop = stop;
}());