doc: improvements to console.markdown copy
[platform/upstream/nodejs.git] / lib / _debug_agent.js
1 'use strict';
2
3 const assert = require('assert');
4 const net = require('net');
5 const util = require('util');
6 const Buffer = require('buffer').Buffer;
7 const Transform = require('stream').Transform;
8
9 exports.start = function start() {
10   var agent = new Agent();
11
12   // Do not let `agent.listen()` request listening from cluster master
13   const cluster = require('cluster');
14   cluster.isWorker = false;
15   cluster.isMaster = true;
16
17   agent.on('error', function(err) {
18     process._rawDebug(err.stack || err);
19   });
20
21   agent.listen(process._debugAPI.port, function() {
22     var addr = this.address();
23     process._rawDebug('Debugger listening on port %d', addr.port);
24     process._debugAPI.notifyListen();
25   });
26
27   // Just to spin-off events
28   // TODO(indutny): Figure out why node.cc isn't doing this
29   setImmediate(function() {
30   });
31
32   process._debugAPI.onclose = function() {
33     // We don't care about it, but it prevents loop from cleaning up gently
34     // NOTE: removeAllListeners won't work, as it doesn't call `removeListener`
35     process.listeners('SIGWINCH').forEach(function(fn) {
36       process.removeListener('SIGWINCH', fn);
37     });
38
39     agent.close();
40   };
41
42   // Not used now, but anyway
43   return agent;
44 };
45
46 function Agent() {
47   net.Server.call(this, this.onConnection);
48
49   this.first = true;
50   this.binding = process._debugAPI;
51
52   var self = this;
53   this.binding.onmessage = function(msg) {
54     self.clients.forEach(function(client) {
55       client.send({}, msg);
56     });
57   };
58
59   this.clients = [];
60   assert(this.binding, 'Debugger agent running without bindings!');
61 }
62 util.inherits(Agent, net.Server);
63
64 Agent.prototype.onConnection = function onConnection(socket) {
65   var c = new Client(this, socket);
66
67   c.start();
68   this.clients.push(c);
69
70   var self = this;
71   c.once('close', function() {
72     var index = self.clients.indexOf(c);
73     assert(index !== -1);
74     self.clients.splice(index, 1);
75   });
76 };
77
78 Agent.prototype.notifyWait = function notifyWait() {
79   if (this.first)
80     this.binding.notifyWait();
81   this.first = false;
82 };
83
84 function Client(agent, socket) {
85   Transform.call(this, {
86     readableObjectMode: true
87   });
88
89   this.agent = agent;
90   this.binding = this.agent.binding;
91   this.socket = socket;
92
93   // Parse incoming data
94   this.state = 'headers';
95   this.headers = {};
96   this.buffer = '';
97   socket.pipe(this);
98
99   this.on('data', this.onCommand);
100
101   var self = this;
102   this.socket.on('close', function() {
103     self.destroy();
104   });
105 }
106 util.inherits(Client, Transform);
107
108 Client.prototype.destroy = function destroy(msg) {
109   this.socket.destroy();
110
111   this.emit('close');
112 };
113
114 Client.prototype._transform = function _transform(data, enc, cb) {
115   cb();
116
117   this.buffer += data;
118
119   while (true) {
120     if (this.state === 'headers') {
121       // Not enough data
122       if (!/\r\n/.test(this.buffer))
123         break;
124
125       if (/^\r\n/.test(this.buffer)) {
126         this.buffer = this.buffer.slice(2);
127         this.state = 'body';
128         continue;
129       }
130
131       // Match:
132       //   Header-name: header-value\r\n
133       var match = this.buffer.match(/^([^:\s\r\n]+)\s*:\s*([^\s\r\n]+)\r\n/);
134       if (!match)
135         return this.destroy('Expected header, but failed to parse it');
136
137       this.headers[match[1].toLowerCase()] = match[2];
138
139       this.buffer = this.buffer.slice(match[0].length);
140     } else {
141       var len = this.headers['content-length'];
142       if (len === undefined)
143         return this.destroy('Expected content-length');
144
145       len = len | 0;
146       if (Buffer.byteLength(this.buffer) < len)
147         break;
148
149       this.push(new Command(this.headers, this.buffer.slice(0, len)));
150       this.state = 'headers';
151       this.buffer = this.buffer.slice(len);
152       this.headers = {};
153     }
154   }
155 };
156
157 Client.prototype.send = function send(headers, data) {
158   if (!data)
159     data = '';
160
161   var out = [];
162   Object.keys(headers).forEach(function(key) {
163     out.push(key + ': ' + headers[key]);
164   });
165   out.push('Content-Length: ' + Buffer.byteLength(data), '');
166
167   this.socket.cork();
168   this.socket.write(out.join('\r\n') + '\r\n');
169
170   if (data.length > 0)
171     this.socket.write(data);
172   this.socket.uncork();
173 };
174
175 Client.prototype.start = function start() {
176   this.send({
177     Type: 'connect',
178     'V8-Version': process.versions.v8,
179     'Protocol-Version': 1,
180     'Embedding-Host': 'node ' + process.version
181   });
182 };
183
184 Client.prototype.onCommand = function onCommand(cmd) {
185   this.binding.sendCommand(cmd.body);
186
187   this.agent.notifyWait();
188 };
189
190 function Command(headers, body) {
191   this.headers = headers;
192   this.body = body;
193 }