You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

267 lines
5.2 KiB

  1. /**
  2. * Module requirements.
  3. */
  4. var Transport = require('../transport')
  5. , parser = require('engine.io-parser')
  6. , debug = require('debug')('engine:polling');
  7. /**
  8. * Exports the constructor.
  9. */
  10. module.exports = Polling;
  11. /**
  12. * HTTP polling constructor.
  13. *
  14. * @api public.
  15. */
  16. function Polling (req) {
  17. Transport.call(this, req);
  18. }
  19. /**
  20. * Inherits from Transport.
  21. *
  22. * @api public.
  23. */
  24. Polling.prototype.__proto__ = Transport.prototype;
  25. /**
  26. * Transport name
  27. *
  28. * @api public
  29. */
  30. Polling.prototype.name = 'polling';
  31. /**
  32. * Overrides onRequest.
  33. *
  34. * @param {http.ServerRequest}
  35. * @api private
  36. */
  37. Polling.prototype.onRequest = function (req) {
  38. var res = req.res;
  39. if ('GET' == req.method) {
  40. this.onPollRequest(req, res);
  41. } else if ('POST' == req.method) {
  42. this.onDataRequest(req, res);
  43. } else {
  44. res.writeHead(500);
  45. res.end();
  46. }
  47. };
  48. /**
  49. * The client sends a request awaiting for us to send data.
  50. *
  51. * @api private
  52. */
  53. Polling.prototype.onPollRequest = function (req, res) {
  54. if (this.req) {
  55. debug('request overlap');
  56. // assert: this.res, '.req and .res should be (un)set together'
  57. this.onError('overlap from client');
  58. res.writeHead(500);
  59. return;
  60. }
  61. debug('setting request');
  62. this.req = req;
  63. this.res = res;
  64. var self = this;
  65. function onClose () {
  66. self.onError('poll connection closed prematurely');
  67. }
  68. function cleanup () {
  69. req.removeListener('close', onClose);
  70. self.req = self.res = null;
  71. }
  72. req.cleanup = cleanup;
  73. req.on('close', onClose);
  74. this.writable = true;
  75. this.emit('drain');
  76. // if we're still writable but had a pending close, trigger an empty send
  77. if (this.writable && this.shouldClose) {
  78. debug('triggering empty send to append close packet');
  79. this.send([{ type: 'noop' }]);
  80. }
  81. };
  82. /**
  83. * The client sends a request with data.
  84. *
  85. * @api private
  86. */
  87. Polling.prototype.onDataRequest = function (req, res) {
  88. if (this.dataReq) {
  89. // assert: this.dataRes, '.dataReq and .dataRes should be (un)set together'
  90. this.onError('data request overlap from client');
  91. res.writeHead(500);
  92. return;
  93. }
  94. var isBinary = 'application/octet-stream' == req.headers['content-type'];
  95. this.dataReq = req;
  96. this.dataRes = res;
  97. var chunks = isBinary ? new Buffer(0) : '';
  98. var self = this;
  99. function cleanup () {
  100. chunks = isBinary ? new Buffer(0) : '';
  101. req.removeListener('data', onData);
  102. req.removeListener('end', onEnd);
  103. req.removeListener('close', onClose);
  104. self.dataReq = self.dataRes = null;
  105. }
  106. function onClose () {
  107. cleanup();
  108. self.onError('data request connection closed prematurely');
  109. }
  110. function onData (data) {
  111. var contentLength;
  112. if (typeof data == 'string') {
  113. chunks += data;
  114. contentLength = Buffer.byteLength(chunks);
  115. } else {
  116. chunks = Buffer.concat([chunks, data]);
  117. contentLength = chunks.length;
  118. }
  119. if (contentLength > self.maxHttpBufferSize) {
  120. chunks = '';
  121. req.connection.destroy();
  122. }
  123. }
  124. function onEnd () {
  125. self.onData(chunks);
  126. var headers = {
  127. // text/html is required instead of text/plain to avoid an
  128. // unwanted download dialog on certain user-agents (GH-43)
  129. 'Content-Type': 'text/html',
  130. 'Content-Length': 2
  131. };
  132. // prevent XSS warnings on IE
  133. // https://github.com/LearnBoost/socket.io/pull/1333
  134. var ua = req.headers['user-agent'];
  135. if (ua && (~ua.indexOf(';MSIE') || ~ua.indexOf('Trident/'))) {
  136. headers['X-XSS-Protection'] = '0';
  137. }
  138. res.writeHead(200, self.headers(req, headers));
  139. res.end('ok');
  140. cleanup();
  141. }
  142. req.abort = cleanup;
  143. req.on('close', onClose);
  144. req.on('data', onData);
  145. req.on('end', onEnd);
  146. if (!isBinary) req.setEncoding('utf8');
  147. };
  148. /**
  149. * Processes the incoming data payload.
  150. *
  151. * @param {String} encoded payload
  152. * @api private
  153. */
  154. Polling.prototype.onData = function (data) {
  155. debug('received "%s"', data);
  156. var self = this;
  157. var callback = function(packet) {
  158. if ('close' == packet.type) {
  159. debug('got xhr close packet');
  160. self.onClose();
  161. return false;
  162. }
  163. self.onPacket(packet);
  164. };
  165. parser.decodePayload(data, callback);
  166. };
  167. /**
  168. * Writes a packet payload.
  169. *
  170. * @param {Object} packet
  171. * @api private
  172. */
  173. Polling.prototype.send = function (packets) {
  174. if (this.shouldClose) {
  175. debug('appending close packet to payload');
  176. packets.push({ type: 'close' });
  177. this.shouldClose();
  178. this.shouldClose = null;
  179. }
  180. var self = this;
  181. parser.encodePayload(packets, this.supportsBinary, function(data) {
  182. self.write(data);
  183. });
  184. };
  185. /**
  186. * Writes data as response to poll request.
  187. *
  188. * @param {String} data
  189. * @api private
  190. */
  191. Polling.prototype.write = function (data) {
  192. debug('writing "%s"', data);
  193. this.doWrite(data);
  194. this.req.cleanup();
  195. this.writable = false;
  196. };
  197. /**
  198. * Closes the transport.
  199. *
  200. * @api private
  201. */
  202. Polling.prototype.doClose = function (fn) {
  203. debug('closing');
  204. if (this.dataReq) {
  205. // FIXME: should we do this?
  206. debug('aborting ongoing data request');
  207. this.dataReq.abort();
  208. }
  209. if (this.writable) {
  210. debug('transport writable - closing right away');
  211. this.send([{ type: 'close' }]);
  212. fn();
  213. } else {
  214. debug('transport not writable - buffering orderly close');
  215. this.shouldClose = fn;
  216. }
  217. };