xref: /unit/src/nodejs/unit-http/http_server.js (revision 875:dae402cb243f)
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('unit-http/build/Release/unit-http.node');
12const unit_socket = require('unit-http/socket');
13
14const { Socket } = unit_socket;
15
16
17function ServerResponse(req) {
18    EventEmitter.call(this);
19
20    this.headers = {};
21}
22util.inherits(ServerResponse, EventEmitter);
23
24ServerResponse.prototype.statusCode = 200;
25ServerResponse.prototype.statusMessage = undefined;
26ServerResponse.prototype.headers_len = 0;
27ServerResponse.prototype.headers_count = 0;
28ServerResponse.prototype.headersSent = false;
29ServerResponse.prototype.finished = false;
30
31ServerResponse.prototype._finish = function _finish() {
32    this.headers = {};
33    this.headers_len = 0;
34    this.headers_count = 0;
35    this.finished = true;
36};
37
38ServerResponse.prototype.assignSocket = function assignSocket(socket) {
39};
40
41ServerResponse.prototype.detachSocket = function detachSocket(socket) {
42};
43
44ServerResponse.prototype.writeContinue = function writeContinue(cb) {
45};
46
47ServerResponse.prototype.writeProcessing = function writeProcessing(cb) {
48};
49
50ServerResponse.prototype.setHeader = function setHeader(name, value) {
51    if (typeof name !== 'string') {
52        throw new TypeError('Name argument must be a string');
53    }
54
55    let value_len = 0
56    let count = 0;
57
58    if (Array.isArray(value)) {
59        count = value.length;
60
61        value.forEach(function(val) {
62            if (typeof val !== 'string' && typeof val !== 'number') {
63                throw new TypeError('Array entries must be string or number');
64            }
65
66            value_len += Buffer.byteLength(val + "", 'latin1');
67        });
68
69    } else {
70        if (typeof value !== 'string' && typeof value !== 'number') {
71            throw new TypeError('Value argument must be string, number, or array');
72        }
73
74        count = 1;
75        value_len = Buffer.byteLength(value + "", 'latin1');
76    }
77
78    let lc_name = name.toLowerCase();
79
80    if (lc_name in this.headers) {
81        this._removeHeader(lc_name);
82    }
83
84    let name_len = Buffer.byteLength(name, 'latin1');
85
86    this.headers[lc_name] = [name, value];
87    this.headers_len += value_len + (name_len * count);
88    this.headers_count += count;
89};
90
91ServerResponse.prototype.getHeader = function getHeader(name) {
92    const entry = this.headers[name.toLowerCase()];
93
94    return entry && entry[1];
95};
96
97ServerResponse.prototype.getHeaderNames = function getHeaderNames() {
98    return Object.keys(this.headers);
99};
100
101ServerResponse.prototype.getHeaders = function getHeaders() {
102    const ret = Object.create(null);
103
104    if (this.headers) {
105        const keys = Object.keys(this.headers);
106
107        for (var i = 0; i < keys.length; i++) {
108            const key = keys[i];
109
110            ret[key] = this.headers[key][1];
111        }
112    }
113
114    return ret;
115};
116
117ServerResponse.prototype.hasHeader = function hasHeader(name) {
118    return name.toLowerCase() in this.headers;
119};
120
121ServerResponse.prototype.removeHeader = function removeHeader(name) {
122    if (typeof name !== 'string') {
123        throw new TypeError('Name argument must be a string');
124    }
125
126    let lc_name = name.toLowerCase();
127
128    if (lc_name in this.headers) {
129        this._removeHeader(lc_name);
130    }
131};
132
133ServerResponse.prototype._removeHeader = function _removeHeader(lc_name) {
134    let entry = this.headers[lc_name];
135    let name_len = Buffer.byteLength(entry[0] + "", 'latin1');
136    let value = entry[1];
137
138    delete this.headers[lc_name];
139
140    if (Array.isArray(value)) {
141        this.headers_count -= value.length;
142        this.headers_len -= value.length * name_len;
143
144        value.forEach(function(val) {
145            this.headers_len -= Buffer.byteLength(val + "", 'latin1');
146        });
147
148        return;
149    }
150
151    this.headers_count--;
152    this.headers_len -= name_len + Buffer.byteLength(value + "", 'latin1');
153};
154
155ServerResponse.prototype.sendDate = function sendDate() {
156    throw new Error("Not supported");
157};
158
159ServerResponse.prototype.setTimeout = function setTimeout(msecs, callback) {
160    this.timeout = msecs;
161
162    if (callback) {
163        this.on('timeout', callback);
164    }
165
166    return this;
167};
168
169// for Express
170ServerResponse.prototype._implicitHeader = function _implicitHeader() {
171    this.writeHead(this.statusCode);
172};
173
174ServerResponse.prototype.writeHead = writeHead;
175ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead;
176
177function writeHead(statusCode, reason, obj) {
178    var originalStatusCode = statusCode;
179
180    statusCode |= 0;
181
182    if (statusCode < 100 || statusCode > 999) {
183        throw new ERR_HTTP_INVALID_STATUS_CODE(originalStatusCode);
184    }
185
186    if (typeof reason === 'string') {
187        this.statusMessage = reason;
188
189    } else {
190        if (!this.statusMessage) {
191            this.statusMessage = http.STATUS_CODES[statusCode] || 'unknown';
192        }
193
194        obj = reason;
195    }
196
197    this.statusCode = statusCode;
198
199    if (obj) {
200        var k;
201        var keys = Object.keys(obj);
202
203        for (var i = 0; i < keys.length; i++) {
204            k = keys[i];
205
206            if (k) {
207                this.setHeader(k, obj[k]);
208            }
209        }
210    }
211};
212
213ServerResponse.prototype._writeBody = function(chunk, encoding, callback) {
214    var contentLength = 0;
215
216    if (!this.headersSent) {
217        unit_lib.unit_response_headers(this, this.statusCode, this.headers,
218                                       this.headers_count, this.headers_len);
219
220        this.headersSent = true;
221    }
222
223    if (typeof chunk === 'function') {
224        callback = chunk;
225        chunk = null;
226
227    } else if (typeof encoding === 'function') {
228        callback = encoding;
229        encoding = null;
230    }
231
232    if (chunk) {
233        if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
234            throw new TypeError('First argument must be a string or Buffer');
235        }
236
237        if (typeof chunk === 'string') {
238            contentLength = Buffer.byteLength(chunk, encoding);
239
240        } else {
241            contentLength = chunk.length;
242        }
243
244        unit_lib.unit_response_write(this, chunk, contentLength);
245    }
246
247    if (typeof callback === 'function') {
248        /*
249         * The callback must be called only when response.write() caller
250         * completes.  process.nextTick() postpones the callback execution.
251         *
252         * process.nextTick() is not technically part of the event loop.
253         * Instead, the nextTickQueue will be processed after the current
254         * operation completes, regardless of the current phase of
255         * the event loop.  All callbacks passed to process.nextTick()
256         * will be resolved before the event loop continues.
257         */
258        process.nextTick(function () {
259            callback(this);
260        }.bind(this));
261    }
262};
263
264ServerResponse.prototype.write = function write(chunk, encoding, callback) {
265    if (this.finished) {
266        throw new Error("Write after end");
267    }
268
269    this._writeBody(chunk, encoding, callback);
270
271    return true;
272};
273
274ServerResponse.prototype.end = function end(chunk, encoding, callback) {
275    if (!this.finished) {
276        this._writeBody(chunk, encoding, callback);
277
278        unit_lib.unit_response_end(this);
279
280        this.finished = true;
281    }
282
283    return this;
284};
285
286function ServerRequest(server) {
287    EventEmitter.call(this);
288
289    this.server = server;
290}
291util.inherits(ServerRequest, EventEmitter);
292
293ServerRequest.prototype.unpipe = undefined;
294
295ServerRequest.prototype.setTimeout = function setTimeout(msecs, callback) {
296    this.timeout = msecs;
297
298    if (callback) {
299        this.on('timeout', callback);
300    }
301
302    return this;
303};
304
305ServerRequest.prototype.statusCode = function statusCode() {
306    /* Only valid for response obtained from http.ClientRequest. */
307};
308
309ServerRequest.prototype.statusMessage = function statusMessage() {
310    /* Only valid for response obtained from http.ClientRequest. */
311};
312
313ServerRequest.prototype.trailers = function trailers() {
314    throw new Error("Not supported");
315};
316
317ServerRequest.prototype.METHODS = function METHODS() {
318    return http.METHODS;
319};
320
321ServerRequest.prototype.STATUS_CODES = function STATUS_CODES() {
322    return http.STATUS_CODES;
323};
324
325ServerRequest.prototype.listeners = function listeners() {
326    return [];
327};
328
329ServerRequest.prototype.resume = function resume() {
330    return [];
331};
332
333/*
334 * The "on" method is overridden to defer reading data until user code is
335 * ready, that is (ev === "data").  This can occur after req.emit("end") is
336 * executed, since the user code can be scheduled asynchronously by Promises
337 * and so on.  Passing the data is postponed by process.nextTick() until
338 * the "on" method caller completes.
339 */
340ServerRequest.prototype.on = function on(ev, fn) {
341    Server.prototype.on.call(this, ev, fn);
342
343    if (ev === "data") {
344        process.nextTick(function () {
345            if (this.server.buffer.length !== 0) {
346                this.emit("data", this.server.buffer);
347            }
348
349        }.bind(this));
350    }
351};
352
353ServerRequest.prototype.addListener = ServerRequest.prototype.on;
354
355function Server(requestListener) {
356    EventEmitter.call(this);
357
358    this.unit = new unit_lib.Unit();
359    this.unit.server = this;
360
361    this.unit.createServer();
362
363    this.socket = Socket;
364    this.request = ServerRequest;
365    this.response = ServerResponse;
366
367    if (requestListener) {
368        this.on('request', requestListener);
369    }
370}
371util.inherits(Server, EventEmitter);
372
373Server.prototype.setTimeout = function setTimeout(msecs, callback) {
374    this.timeout = msecs;
375
376    if (callback) {
377        this.on('timeout', callback);
378    }
379
380    return this;
381};
382
383Server.prototype.listen = function () {
384    this.unit.listen();
385};
386
387Server.prototype.emit_events = function (server, req, res) {
388    req.server = server;
389    res.server = server;
390    req.res = res;
391    res.req = req;
392
393    server.buffer = server.unit._read(req.socket.req_pointer);
394
395    server.emit("request", req, res);
396
397    process.nextTick(() => {
398        req.emit("finish");
399        req.emit("end");
400    });
401};
402
403function connectionListener(socket) {
404}
405
406module.exports = {
407    STATUS_CODES: http.STATUS_CODES,
408    Server,
409    ServerResponse,
410    ServerRequest,
411    _connectionListener: connectionListener
412};
413