diff options
Diffstat (limited to 'deps/debugger-agent/lib/_debugger_agent.js')
-rw-r--r-- | deps/debugger-agent/lib/_debugger_agent.js | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/deps/debugger-agent/lib/_debugger_agent.js b/deps/debugger-agent/lib/_debugger_agent.js new file mode 100644 index 0000000000..680c5e95c4 --- /dev/null +++ b/deps/debugger-agent/lib/_debugger_agent.js @@ -0,0 +1,191 @@ +var assert = require('assert'); +var net = require('net'); +var util = require('util'); +var Buffer = require('buffer').Buffer; + +var Transform = require('stream').Transform; + +exports.start = function start() { + var agent = new Agent(); + + // Do not let `agent.listen()` request listening from cluster master + var cluster = require('cluster'); + cluster.isWorker = false; + cluster.isMaster = true; + + agent.on('error', function(err) { + process._rawDebug(err.stack || err); + }); + + agent.listen(process._debugAPI.port, function() { + var addr = this.address(); + process._rawDebug('Debugger listening on port %d', addr.port); + process._debugAPI.notifyListen(); + }); + + // Just to spin-off events + // TODO(indutny): Figure out why node.cc isn't doing this + setImmediate(function() { + }); + + process._debugAPI.onclose = function() { + // We don't care about it, but it prevents loop from cleaning up gently + // NOTE: removeAllListeners won't work, as it doesn't call `removeListener` + process.listeners('SIGWINCH').forEach(function(fn) { + process.removeListener('SIGWINCH', fn); + }); + + agent.close(); + }; + + // Not used now, but anyway + return agent; +}; + +function Agent() { + net.Server.call(this, this.onConnection); + + this.first = true; + this.binding = process._debugAPI; + + var self = this; + this.binding.onmessage = function(msg) { + self.clients.forEach(function(client) { + client.send({}, msg); + }); + }; + + this.clients = []; + assert(this.binding, 'Debugger agent running without bindings!'); +} +util.inherits(Agent, net.Server); + +Agent.prototype.onConnection = function onConnection(socket) { + var c = new Client(this, socket); + + c.start(); + this.clients.push(c); + + var self = this; + c.once('close', function() { + var index = self.clients.indexOf(c); + assert(index !== -1); + self.clients.splice(index, 1); + }); +}; + +Agent.prototype.notifyWait = function notifyWait() { + if (this.first) + this.binding.notifyWait(); + this.first = false; +}; + +function Client(agent, socket) { + Transform.call(this); + this._readableState.objectMode = true; + + this.agent = agent; + this.binding = this.agent.binding; + this.socket = socket; + + // Parse incoming data + this.state = 'headers'; + this.headers = {}; + this.buffer = ''; + socket.pipe(this); + + this.on('data', this.onCommand); + + var self = this; + this.socket.on('close', function() { + self.destroy(); + }); +} +util.inherits(Client, Transform); + +Client.prototype.destroy = function destroy(msg) { + this.socket.destroy(); + + this.emit('close'); +}; + +Client.prototype._transform = function _transform(data, enc, cb) { + cb(); + + this.buffer += data; + + while (true) { + if (this.state === 'headers') { + // Not enough data + if (!/\r\n/.test(this.buffer)) + break; + + if (/^\r\n/.test(this.buffer)) { + this.buffer = this.buffer.slice(2); + this.state = 'body'; + continue; + } + + // Match: + // Header-name: header-value\r\n + var match = this.buffer.match(/^([^:\s\r\n]+)\s*:\s*([^\s\r\n]+)\r\n/); + if (!match) + return this.destroy('Expected header, but failed to parse it'); + + this.headers[match[1].toLowerCase()] = match[2]; + + this.buffer = this.buffer.slice(match[0].length); + } else { + var len = this.headers['content-length']; + if (len === undefined) + return this.destroy('Expected content-length'); + + len = len | 0; + if (Buffer.byteLength(this.buffer) < len) + break; + + this.push(new Command(this.headers, this.buffer.slice(0, len))); + this.state = 'headers'; + this.buffer = this.buffer.slice(len); + this.headers = {}; + } + } +}; + +Client.prototype.send = function send(headers, data) { + if (!data) + data = ''; + + var out = []; + Object.keys(headers).forEach(function(key) { + out.push(key + ': ' + headers[key]); + }); + out.push('Content-Length: ' + Buffer.byteLength(data), ''); + + this.socket.cork(); + this.socket.write(out.join('\r\n') + '\r\n'); + + if (data.length > 0) + this.socket.write(data); + this.socket.uncork(); +}; + +Client.prototype.start = function start() { + this.send({ + Type: 'connect', + 'V8-Version': process.versions.v8, + 'Protocol-Version': 1, + 'Embedding-Host': 'node ' + process.version + }); +}; + +Client.prototype.onCommand = function onCommand(cmd) { + this.binding.sendCommand(cmd.body); + + this.agent.notifyWait(); +}; + +function Command(headers, body) { + this.headers = headers; + this.body = body; +} |