source: trip-planner-front/node_modules/engine.io/lib/transports/polling.js@ ceaed42

Last change on this file since ceaed42 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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