192 строки
4.5 KiB
JavaScript
192 строки
4.5 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const net = require('net');
|
|
const util = require('util');
|
|
const Buffer = require('buffer').Buffer;
|
|
const Transform = require('stream').Transform;
|
|
|
|
exports.start = function start() {
|
|
var agent = new Agent();
|
|
|
|
// Do not let `agent.listen()` request listening from cluster master
|
|
const cluster = require('cluster');
|
|
cluster.isWorker = false;
|
|
cluster.isMaster = true;
|
|
|
|
agent.on('error', function(err) {
|
|
process._rawDebug(err.stack || err);
|
|
});
|
|
|
|
agent.listen(process._debugAPI.port, process._debugAPI.host, function() {
|
|
const addr = this.address();
|
|
const host = net.isIPv6(addr.address) ? `[${addr.address}]` : addr.address;
|
|
process._rawDebug('Debugger listening on %s:%d', host, 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;
|
|
assert(this.binding, 'Debugger agent running without bindings!');
|
|
|
|
this.binding.onmessage = (msg) => {
|
|
this.clients.forEach((client) => {
|
|
client.send({}, msg);
|
|
});
|
|
};
|
|
|
|
this.clients = [];
|
|
}
|
|
util.inherits(Agent, net.Server);
|
|
|
|
Agent.prototype.onConnection = function onConnection(socket) {
|
|
var c = new Client(this, socket);
|
|
|
|
c.start();
|
|
this.clients.push(c);
|
|
|
|
c.once('close', () => {
|
|
var index = this.clients.indexOf(c);
|
|
assert(index !== -1);
|
|
this.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, {
|
|
readableObjectMode: 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);
|
|
|
|
this.socket.on('close', () => {
|
|
this.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 (!this.buffer.includes('\r\n'))
|
|
break;
|
|
|
|
if (this.buffer.startsWith('\r\n')) {
|
|
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;
|
|
}
|