Files
react-native/packager/react-packager/src/SocketInterface/SocketServer.js
2015-08-25 17:36:08 -08:00

173 lines
4.3 KiB
JavaScript

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const Promise = require('promise');
const Server = require('../Server');
const bser = require('bser');
const debug = require('debug')('ReactPackager:SocketServer');
const fs = require('fs');
const net = require('net');
const MAX_IDLE_TIME = 10 * 60 * 1000;
class SocketServer {
constructor(sockPath, options) {
this._server = net.createServer();
this._server.listen(sockPath);
this._ready = new Promise((resolve, reject) => {
this._server.on('error', (e) => reject(e));
this._server.on('listening', () => {
debug(
'Process %d listening on socket path %s ' +
'for server with options %j',
process.pid,
sockPath,
options
);
resolve(this);
});
});
this._server.on('connection', (sock) => this._handleConnection(sock));
// Disable the file watcher.
options.nonPersistent = true;
this._packagerServer = new Server(options);
this._jobs = 0;
this._dieEventually();
process.on('exit', () => fs.unlinkSync(sockPath));
}
onReady() {
return this._ready;
}
_handleConnection(sock) {
debug('connection to server', process.pid);
const bunser = new bser.BunserBuf();
sock.on('data', (buf) => bunser.append(buf));
bunser.on('value', (m) => this._handleMessage(sock, m));
}
_handleMessage(sock, m) {
if (!m || !m.id || !m.data) {
console.error('SocketServer recieved a malformed message: %j', m);
return;
}
debug('got request', m);
// Debounce the kill timer.
this._dieEventually();
const handleError = (error) => {
debug('request error', error);
this._jobs--;
this._reply(sock, m.id, 'error', error.stack);
};
switch (m.type) {
case 'getDependencies':
this._jobs++;
this._packagerServer.getDependencies(m.data).then(
({ dependencies }) => this._reply(sock, m.id, 'result', dependencies),
handleError,
);
break;
case 'buildBundle':
this._jobs++;
this._packagerServer.buildBundle(m.data).then(
(result) => this._reply(sock, m.id, 'result', result),
handleError,
);
break;
default:
this._reply(sock, m.id, 'error', 'Unknown message type: ' + m.type);
}
}
_reply(sock, id, type, data) {
debug('request finished', type);
this._jobs--;
data = toJSON(data);
sock.write(bser.dumpToBuffer({
id,
type,
data,
}));
}
_dieEventually() {
clearTimeout(this._deathTimer);
this._deathTimer = setTimeout(() => {
if (this._jobs <= 0) {
debug('server dying', process.pid);
process.exit(1);
}
this._dieEventually();
}, MAX_IDLE_TIME);
}
static listenOnServerIPCMessages() {
process.on('message', (message) => {
if (!(message && message.type && message.type === 'createSocketServer')) {
return;
}
debug('server got ipc message', message);
const {options, sockPath} = message.data;
// regexp doesn't naturally serialize to json.
options.blacklistRE = new RegExp(options.blacklistRE.source);
new SocketServer(sockPath, options).onReady().then(
() => {
debug('succesfully created server');
process.send({ type: 'createdServer' });
},
error => {
debug('error creating server', error.code);
if (error.code === 'EADDRINUSE') {
// Server already listening, this may happen if multiple
// clients where started in quick succussion (buck).
process.send({ type: 'createdServer' });
} else {
throw error;
}
}
).done();
});
}
}
// TODO move this to bser code.
function toJSON(object) {
if (!(object && typeof object === 'object')) {
return object;
}
if (object.toJSON) {
return object.toJSON();
}
for (var p in object) {
object[p] = toJSON(object[p]);
}
return object;
}
module.exports = SocketServer;