xref: /unit/src/nodejs/unit-http/http_server.js (revision 2649:7621e8f40ef6)
1
2/*
3 * Copyright (C) NGINX, Inc.
4 */
5
6'use strict';
7
8const EventEmitter = require('events');
9const http = require('http');
10const util = require('util');
11const unit_lib = require('./build/Release/unit-http');
12const Socket = require('./socket');
13const WebSocketFrame = require('./websocket_frame');
14const Readable = require('stream').Readable;
15
16
17function ServerResponse(req) {
18    EventEmitter.call(this);
19
20    this.headers = {};
21
22    this.server = req.server;
23    this._request = req;
24    req._response = this;
25    this.socket = req.socket;
26    this.connection = req.connection;
27    this.writable = true;
28}
29util.inherits(ServerResponse, EventEmitter);
30
31ServerResponse.prototype.statusCode = 200;
32ServerResponse.prototype.statusMessage = undefined;
33ServerResponse.prototype.headers_len = 0;
34ServerResponse.prototype.headers_count = 0;
35ServerResponse.prototype.headersSent = false;
36ServerResponse.prototype.destroyed = false;
37ServerResponse.prototype.finished = false;
38
39ServerResponse.prototype.destroy = function destroy(error) {
40    if (!this.destroyed) {
41        this.destroyed = true;
42    }
43
44    return this;
45};
46
47ServerResponse.prototype._finish = function _finish() {
48    this.headers = {};
49    this.headers_len = 0;
50    this.headers_count = 0;
51    this.finished = true;
52};
53
54ServerResponse.prototype.assignSocket = function assignSocket(socket) {
55};
56
57ServerResponse.prototype.detachSocket = function detachSocket(socket) {
58};
59
60ServerResponse.prototype.writeContinue = function writeContinue(cb) {
61};
62
63ServerResponse.prototype.writeProcessing = function writeProcessing(cb) {
64};
65
66ServerResponse.prototype.setHeader = function setHeader(name, value) {
67    if (typeof name !== 'string') {
68        throw new TypeError('Name argument must be a string');
69    }
70
71    let value_len = 0
72    let count = 0;
73
74    if (Array.isArray(value)) {
75        count = value.length;
76
77        value.forEach(function(val) {
78            value_len += Buffer.byteLength(val + "", 'latin1');
79        });
80
81    } else {
82        count = 1;
83        value_len = Buffer.byteLength(value + "", 'latin1');
84    }
85
86    let lc_name = name.toLowerCase();
87
88    if (lc_name in this.headers) {
89        this._removeHeader(lc_name);
90    }
91
92    let name_len = Buffer.byteLength(name, 'latin1');
93
94    this.headers[lc_name] = [name, value];
95    this.headers_len += value_len + (name_len * count);
96    this.headers_count += count;
97};
98
99ServerResponse.prototype.getHeader = function getHeader(name) {
100    const entry = this.headers[name.toLowerCase()];
101
102    return entry && entry[1];
103};
104
105ServerResponse.prototype.getHeaderNames = function getHeaderNames() {
106    return Object.keys(this.headers);
107};
108
109ServerResponse.prototype.getHeaders = function getHeaders() {
110    const ret = Object.create(null);
111
112    if (this.headers) {
113        const keys = Object.keys(this.headers);
114
115        for (var i = 0; i < keys.length; i++) {
116            const key = keys[i];
117
118            ret[key] = this.headers[key][1];
119        }
120    }
121
122    return ret;
123};
124
125ServerResponse.prototype.hasHeader = function hasHeader(name) {
126    return name.toLowerCase() in this.headers;
127};
128
129ServerResponse.prototype.removeHeader = function removeHeader(name) {
130    if (typeof name !== 'string') {
131        throw new TypeError('Name argument must be a string');
132    }
133
134    let lc_name = name.toLowerCase();
135
136    if (lc_name in this.headers) {
137        this._removeHeader(lc_name);
138    }
139};
140
141ServerResponse.prototype.flushHeaders = function flushHeaders() {
142    this._sendHeaders();
143};
144
145ServerResponse.prototype._removeHeader = function _removeHeader(lc_name) {
146    let entry = this.headers[lc_name];
147    let name_len = Buffer.byteLength(entry[0] + "", 'latin1');
148    let value = entry[1];
149
150    delete this.headers[lc_name];
151
152    if (Array.isArray(value)) {
153        this.headers_count -= value.length;
154        this.headers_len -= value.length * name_len;
155
156        value.forEach(function(val) {
157            this.headers_len -= Buffer.byteLength(val + "", 'latin1');
158        });
159
160        return;
161    }
162
163    this.headers_count--;
164    this.headers_len -= name_len + Buffer.byteLength(value + "", 'latin1');
165};
166
167ServerResponse.prototype.sendDate = function sendDate() {
168    throw new Error("Not supported");
169};
170
171ServerResponse.prototype.setTimeout = function setTimeout(msecs, callback) {
172    this.timeout = msecs;
173
174    if (callback) {
175        this.on('timeout', callback);
176    }
177
178    return this;
179};
180
181ServerResponse.prototype.writeHead = writeHead;
182ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead;
183
184function writeHead(statusCode, reason, obj) {
185    var originalStatusCode = statusCode;
186
187    statusCode |= 0;
188
189    if (statusCode < 100 || statusCode > 999) {
190        throw new ERR_HTTP_INVALID_STATUS_CODE(originalStatusCode);
191    }
192
193    if (typeof reason === 'string') {
194        this.statusMessage = reason;
195
196    } else {
197        if (!this.statusMessage) {
198            this.statusMessage = http.STATUS_CODES[statusCode] || 'unknown';
199        }
200
201        obj = reason;
202    }
203
204    this.statusCode = statusCode;
205
206    if (obj) {
207        var k;
208        var keys = Object.keys(obj);
209
210        for (var i = 0; i < keys.length; i++) {
211            k = keys[i];
212
213            if (k) {
214                this.setHeader(k, obj[k]);
215            }
216        }
217    }
218
219    return this;
220};
221
222/*
223 * Some Node.js packages are known to be using this undocumented function,
224 * notably "compression" middleware.
225 */
226ServerResponse.prototype._implicitHeader = function _implicitHeader() {
227    this.writeHead(this.statusCode);
228};
229
230ServerResponse.prototype._send_headers = unit_lib.response_send_headers;
231
232ServerResponse.prototype._sendHeaders = function _sendHeaders() {
233    if (!this.headersSent) {
234        this._send_headers(this.statusCode, this.headers, this.headers_count,
235                           this.headers_len);
236
237        this.headersSent = true;
238    }
239};
240
241ServerResponse.prototype._write = unit_lib.response_write;
242
243ServerResponse.prototype._writeBody = function(chunk, encoding, callback) {
244    var contentLength = 0;
245    var res, o;
246
247    this._sendHeaders();
248
249    if (typeof chunk === 'function') {
250        callback = chunk;
251        chunk = null;
252
253    } else if (typeof encoding === 'function') {
254        callback = encoding;
255        encoding = null;
256    }
257
258    if (chunk) {
259        if (typeof chunk !== 'string' && !(chunk instanceof Buffer ||
260                chunk instanceof Uint8Array)) {
261            throw new TypeError(
262                'First argument must be a string, Buffer, ' +
263                'or Uint8Array');
264        }
265
266        if (typeof chunk === 'string') {
267            contentLength = Buffer.byteLength(chunk, encoding);
268
269            if (contentLength > unit_lib.buf_min) {
270                chunk = Buffer.from(chunk, encoding);
271
272                contentLength = chunk.length;
273            }
274
275        } else {
276            contentLength = chunk.length;
277        }
278
279        if (this.server._output.length > 0 || !this.socket.writable) {
280            o = new BufferedOutput(this, 0, chunk, encoding, callback);
281            this.server._output.push(o);
282
283            return false;
284        }
285
286        res = this._write(chunk, 0, contentLength);
287        if (res < contentLength) {
288            this.socket.writable = false;
289            this.writable = false;
290
291            o = new BufferedOutput(this, res, chunk, encoding, callback);
292            this.server._output.push(o);
293
294            return false;
295        }
296    }
297
298    if (typeof callback === 'function') {
299        /*
300         * The callback must be called only when response.write() caller
301         * completes.  process.nextTick() postpones the callback execution.
302         *
303         * process.nextTick() is not technically part of the event loop.
304         * Instead, the nextTickQueue will be processed after the current
305         * operation completes, regardless of the current phase of
306         * the event loop.  All callbacks passed to process.nextTick()
307         * will be resolved before the event loop continues.
308         */
309        process.nextTick(callback);
310    }
311
312    return true;
313};
314
315ServerResponse.prototype.write = function write(chunk, encoding, callback) {
316    if (this.finished) {
317        if (typeof encoding === 'function') {
318            callback = encoding;
319            encoding = null;
320        }
321
322        var err = new Error("Write after end");
323        process.nextTick(() => {
324            this.emit('error', err);
325
326            if (typeof callback === 'function') {
327                callback(err);
328            }
329        })
330    }
331
332    return this._writeBody(chunk, encoding, callback);
333};
334
335ServerResponse.prototype._end = unit_lib.response_end;
336
337ServerResponse.prototype.end = function end(chunk, encoding, callback) {
338    if (!this.finished) {
339        if (typeof encoding === 'function') {
340            callback = encoding;
341            encoding = null;
342        }
343
344        this._writeBody(chunk, encoding, () => {
345            this._end();
346
347            if (typeof callback === 'function') {
348                callback();
349            }
350
351            this.emit("finish");
352        });
353
354        this.finished = true;
355    }
356
357    return this;
358};
359
360function ServerRequest(server, socket) {
361    Readable.call(this);
362
363    this.server = server;
364    this.socket = socket;
365    this.connection = socket;
366    this._pushed_eofchunk = false;
367}
368util.inherits(ServerRequest, Readable);
369
370ServerRequest.prototype.setTimeout = function setTimeout(msecs, callback) {
371    this.timeout = msecs;
372
373    if (callback) {
374        this.on('timeout', callback);
375    }
376
377    return this;
378};
379
380ServerRequest.prototype.statusCode = function statusCode() {
381    /* Only valid for response obtained from http.ClientRequest. */
382};
383
384ServerRequest.prototype.statusMessage = function statusMessage() {
385    /* Only valid for response obtained from http.ClientRequest. */
386};
387
388ServerRequest.prototype.trailers = function trailers() {
389    throw new Error("Not supported");
390};
391
392ServerRequest.prototype.METHODS = function METHODS() {
393    return http.METHODS;
394};
395
396ServerRequest.prototype.STATUS_CODES = function STATUS_CODES() {
397    return http.STATUS_CODES;
398};
399
400ServerRequest.prototype._request_read = unit_lib.request_read;
401
402ServerRequest.prototype._read = function _read(n) {
403    const b = this._request_read(n);
404
405    if (b != null) {
406        this.push(b);
407    }
408
409    if (!this._pushed_eofchunk && (b == null || b.length < n)) {
410        this._pushed_eofchunk = true;
411        this.push(null);
412    }
413};
414
415
416function Server(options, requestListener) {
417    if (typeof options === 'function') {
418        requestListener = options;
419        options = {};
420    } else {
421        console.warn("http.Server constructor was called with unsupported options, using default settings");
422    }
423
424    EventEmitter.call(this);
425
426    this.unit = new unit_lib.Unit();
427    this.unit.server = this;
428
429    this.unit.createServer();
430
431    this.Socket = Socket;
432    this.ServerRequest = ServerRequest;
433    this.ServerResponse = ServerResponse;
434    this.WebSocketFrame = WebSocketFrame;
435
436    if (requestListener) {
437        this.on('request', requestListener);
438    }
439
440    this._upgradeListenerCount = 0;
441    this.on('newListener', function(ev) {
442        if (ev === 'upgrade'){
443            this._upgradeListenerCount++;
444        }
445      }).on('removeListener', function(ev) {
446        if (ev === 'upgrade') {
447            this._upgradeListenerCount--;
448        }
449    });
450
451    this._output = [];
452    this._drain_resp = new Set();
453}
454
455util.inherits(Server, EventEmitter);
456
457Server.prototype.setTimeout = function setTimeout(msecs, callback) {
458    this.timeout = msecs;
459
460    if (callback) {
461        this.on('timeout', callback);
462    }
463
464    return this;
465};
466
467Server.prototype.listen = function (...args) {
468    this.unit.listen();
469
470    if (typeof args[args.length - 1] === 'function') {
471        this.once('listening', args[args.length - 1]);
472    }
473
474    /*
475     * Some express.js apps use the returned server object inside the listening
476     * callback, so we timeout the listening event to occur after this function
477     * returns.
478     */
479    setImmediate(function() {
480        this.emit('listening')
481    }.bind(this))
482
483    return this;
484};
485
486Server.prototype.address = function () {
487    return  {
488        family: "IPv4",
489        address: "127.0.0.1",
490        port: 80
491    }
492}
493
494Server.prototype.emit_request = function (req, res) {
495    if (req._websocket_handshake && this._upgradeListenerCount > 0) {
496        this.emit('upgrade', req, req.socket);
497
498    } else {
499        this.emit("request", req, res);
500    }
501};
502
503Server.prototype.emit_close = function () {
504    this.emit('close');
505};
506
507Server.prototype.emit_drain = function () {
508    var res, o, l;
509
510    if (this._output.length <= 0) {
511        return;
512    }
513
514    while (this._output.length > 0) {
515        o = this._output[0];
516
517        if (typeof o.chunk === 'string') {
518            l = Buffer.byteLength(o.chunk, o.encoding);
519
520        } else {
521            l = o.chunk.length;
522        }
523
524        res = o.resp._write(o.chunk, o.offset, l);
525
526        o.offset += res;
527        if (o.offset < l) {
528            return;
529        }
530
531        this._drain_resp.add(o.resp);
532
533        if (typeof o.callback === 'function') {
534            process.nextTick(o.callback);
535        }
536
537        this._output.shift();
538    }
539
540    for (var resp of this._drain_resp) {
541
542        if (resp.socket.writable) {
543            continue;
544        }
545
546        resp.socket.writable = true;
547        resp.writable = true;
548
549        process.nextTick(() => {
550            resp.emit("drain");
551        });
552    }
553
554    this._drain_resp.clear();
555};
556
557function BufferedOutput(resp, offset, chunk, encoding, callback) {
558    this.resp = resp;
559    this.offset = offset;
560    this.chunk = chunk;
561    this.encoding = encoding;
562    this.callback = callback;
563}
564
565function connectionListener(socket) {
566}
567
568module.exports = {
569    Server,
570    ServerResponse,
571    ServerRequest,
572    _connectionListener: connectionListener
573};
574