[SignalingServer] Optimize dependent modules
[platform/framework/web/wrtjs.git] / signaling_server / service / node_modules / engine.io / lib / transports / polling.js
1
2 /**
3  * Module requirements.
4  */
5
6 var Transport = require('../transport');
7 var parser = require('engine.io-parser');
8 var zlib = require('zlib');
9 var accepts = require('accepts');
10 var util = require('util');
11 var debug = require('debug')('engine:polling');
12
13 var compressionMethods = {
14   gzip: zlib.createGzip,
15   deflate: zlib.createDeflate
16 };
17
18 /**
19  * Exports the constructor.
20  */
21
22 module.exports = Polling;
23
24 /**
25  * HTTP polling constructor.
26  *
27  * @api public.
28  */
29
30 function Polling (req) {
31   Transport.call(this, req);
32
33   this.closeTimeout = 30 * 1000;
34   this.maxHttpBufferSize = null;
35   this.httpCompression = null;
36 }
37
38 /**
39  * Inherits from Transport.
40  *
41  * @api public.
42  */
43
44 util.inherits(Polling, Transport);
45
46 /**
47  * Transport name
48  *
49  * @api public
50  */
51
52 Polling.prototype.name = 'polling';
53
54 /**
55  * Overrides onRequest.
56  *
57  * @param {http.IncomingMessage}
58  * @api private
59  */
60
61 Polling.prototype.onRequest = function (req) {
62   var res = req.res;
63
64   if ('GET' === req.method) {
65     this.onPollRequest(req, res);
66   } else if ('POST' === req.method) {
67     this.onDataRequest(req, res);
68   } else {
69     res.writeHead(500);
70     res.end();
71   }
72 };
73
74 /**
75  * The client sends a request awaiting for us to send data.
76  *
77  * @api private
78  */
79
80 Polling.prototype.onPollRequest = function (req, res) {
81   if (this.req) {
82     debug('request overlap');
83     // assert: this.res, '.req and .res should be (un)set together'
84     this.onError('overlap from client');
85     res.writeHead(500);
86     res.end();
87     return;
88   }
89
90   debug('setting request');
91
92   this.req = req;
93   this.res = res;
94
95   var self = this;
96
97   function onClose () {
98     self.onError('poll connection closed prematurely');
99   }
100
101   function cleanup () {
102     req.removeListener('close', onClose);
103     self.req = self.res = null;
104   }
105
106   req.cleanup = cleanup;
107   req.on('close', onClose);
108
109   this.writable = true;
110   this.emit('drain');
111
112   // if we're still writable but had a pending close, trigger an empty send
113   if (this.writable && this.shouldClose) {
114     debug('triggering empty send to append close packet');
115     this.send([{ type: 'noop' }]);
116   }
117 };
118
119 /**
120  * The client sends a request with data.
121  *
122  * @api private
123  */
124
125 Polling.prototype.onDataRequest = function (req, res) {
126   if (this.dataReq) {
127     // assert: this.dataRes, '.dataReq and .dataRes should be (un)set together'
128     this.onError('data request overlap from client');
129     res.writeHead(500);
130     res.end();
131     return;
132   }
133
134   var isBinary = 'application/octet-stream' === req.headers['content-type'];
135
136   this.dataReq = req;
137   this.dataRes = res;
138
139   var chunks = isBinary ? Buffer.concat([]) : '';
140   var self = this;
141
142   function cleanup () {
143     req.removeListener('data', onData);
144     req.removeListener('end', onEnd);
145     req.removeListener('close', onClose);
146     self.dataReq = self.dataRes = chunks = null;
147   }
148
149   function onClose () {
150     cleanup();
151     self.onError('data request connection closed prematurely');
152   }
153
154   function onData (data) {
155     var contentLength;
156     if (isBinary) {
157       chunks = Buffer.concat([chunks, data]);
158       contentLength = chunks.length;
159     } else {
160       chunks += data;
161       contentLength = Buffer.byteLength(chunks);
162     }
163
164     if (contentLength > self.maxHttpBufferSize) {
165       chunks = isBinary ? Buffer.concat([]) : '';
166       req.connection.destroy();
167     }
168   }
169
170   function onEnd () {
171     self.onData(chunks);
172
173     var headers = {
174       // text/html is required instead of text/plain to avoid an
175       // unwanted download dialog on certain user-agents (GH-43)
176       'Content-Type': 'text/html',
177       'Content-Length': 2
178     };
179
180     res.writeHead(200, self.headers(req, headers));
181     res.end('ok');
182     cleanup();
183   }
184
185   req.on('close', onClose);
186   if (!isBinary) req.setEncoding('utf8');
187   req.on('data', onData);
188   req.on('end', onEnd);
189 };
190
191 /**
192  * Processes the incoming data payload.
193  *
194  * @param {String} encoded payload
195  * @api private
196  */
197
198 Polling.prototype.onData = function (data) {
199   debug('received "%s"', data);
200   var self = this;
201   var callback = function (packet) {
202     if ('close' === packet.type) {
203       debug('got xhr close packet');
204       self.onClose();
205       return false;
206     }
207
208     self.onPacket(packet);
209   };
210
211   parser.decodePayload(data, callback);
212 };
213
214 /**
215  * Overrides onClose.
216  *
217  * @api private
218  */
219
220 Polling.prototype.onClose = function () {
221   if (this.writable) {
222     // close pending poll request
223     this.send([{ type: 'noop' }]);
224   }
225   Transport.prototype.onClose.call(this);
226 };
227
228 /**
229  * Writes a packet payload.
230  *
231  * @param {Object} packet
232  * @api private
233  */
234
235 Polling.prototype.send = function (packets) {
236   this.writable = false;
237
238   if (this.shouldClose) {
239     debug('appending close packet to payload');
240     packets.push({ type: 'close' });
241     this.shouldClose();
242     this.shouldClose = null;
243   }
244
245   var self = this;
246   parser.encodePayload(packets, this.supportsBinary, function (data) {
247     var compress = packets.some(function (packet) {
248       return packet.options && packet.options.compress;
249     });
250     self.write(data, { compress: compress });
251   });
252 };
253
254 /**
255  * Writes data as response to poll request.
256  *
257  * @param {String} data
258  * @param {Object} options
259  * @api private
260  */
261
262 Polling.prototype.write = function (data, options) {
263   debug('writing "%s"', data);
264   var self = this;
265   this.doWrite(data, options, function () {
266     self.req.cleanup();
267   });
268 };
269
270 /**
271  * Performs the write.
272  *
273  * @api private
274  */
275
276 Polling.prototype.doWrite = function (data, options, callback) {
277   var self = this;
278
279   // explicit UTF-8 is required for pages not served under utf
280   var isString = typeof data === 'string';
281   var contentType = isString
282     ? 'text/plain; charset=UTF-8'
283     : 'application/octet-stream';
284
285   var headers = {
286     'Content-Type': contentType
287   };
288
289   if (!this.httpCompression || !options.compress) {
290     respond(data);
291     return;
292   }
293
294   var len = isString ? Buffer.byteLength(data) : data.length;
295   if (len < this.httpCompression.threshold) {
296     respond(data);
297     return;
298   }
299
300   var encoding = accepts(this.req).encodings(['gzip', 'deflate']);
301   if (!encoding) {
302     respond(data);
303     return;
304   }
305
306   this.compress(data, encoding, function (err, data) {
307     if (err) {
308       self.res.writeHead(500);
309       self.res.end();
310       callback(err);
311       return;
312     }
313
314     headers['Content-Encoding'] = encoding;
315     respond(data);
316   });
317
318   function respond (data) {
319     headers['Content-Length'] = 'string' === typeof data ? Buffer.byteLength(data) : data.length;
320     self.res.writeHead(200, self.headers(self.req, headers));
321     self.res.end(data);
322     callback();
323   }
324 };
325
326 /**
327  * Compresses data.
328  *
329  * @api private
330  */
331
332 Polling.prototype.compress = function (data, encoding, callback) {
333   debug('compressing');
334
335   var buffers = [];
336   var nread = 0;
337
338   compressionMethods[encoding](this.httpCompression)
339     .on('error', callback)
340     .on('data', function (chunk) {
341       buffers.push(chunk);
342       nread += chunk.length;
343     })
344     .on('end', function () {
345       callback(null, Buffer.concat(buffers, nread));
346     })
347     .end(data);
348 };
349
350 /**
351  * Closes the transport.
352  *
353  * @api private
354  */
355
356 Polling.prototype.doClose = function (fn) {
357   debug('closing');
358
359   var self = this;
360   var closeTimeoutTimer;
361
362   if (this.dataReq) {
363     debug('aborting ongoing data request');
364     this.dataReq.destroy();
365   }
366
367   if (this.writable) {
368     debug('transport writable - closing right away');
369     this.send([{ type: 'close' }]);
370     onClose();
371   } else if (this.discarded) {
372     debug('transport discarded - closing right away');
373     onClose();
374   } else {
375     debug('transport not writable - buffering orderly close');
376     this.shouldClose = onClose;
377     closeTimeoutTimer = setTimeout(onClose, this.closeTimeout);
378   }
379
380   function onClose () {
381     clearTimeout(closeTimeoutTimer);
382     fn();
383     self.onClose();
384   }
385 };
386
387 /**
388  * Returns headers for a response.
389  *
390  * @param {http.IncomingMessage} request
391  * @param {Object} extra headers
392  * @api private
393  */
394
395 Polling.prototype.headers = function (req, headers) {
396   headers = headers || {};
397
398   // prevent XSS warnings on IE
399   // https://github.com/LearnBoost/socket.io/pull/1333
400   var ua = req.headers['user-agent'];
401   if (ua && (~ua.indexOf(';MSIE') || ~ua.indexOf('Trident/'))) {
402     headers['X-XSS-Protection'] = '0';
403   }
404
405   this.emit('headers', headers);
406   return headers;
407 };