xref: /unit/src/nodejs/unit-http/http_server.js (revision 844:c1938e4e5d86)
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(key, value) {
51    if (typeof key !== 'string') {
52        throw new TypeError('Key argument must be a string');
53    }
54
55    let header_key_len = Buffer.byteLength(key, 'latin1');
56    let header_len = 0
57    let header_count = 0;
58
59    if (Array.isArray(value)) {
60        header_count = value.length;
61
62        value.forEach(function(val) {
63            if (typeof val !== 'string' && typeof val !== 'number') {
64                throw new TypeError('Array entries must be string or number');
65            }
66
67            header_len += Buffer.byteLength(val + "", 'latin1');
68        });
69
70    } else {
71        if (typeof value !== 'string' && typeof value !== 'number') {
72            throw new TypeError('Value argument must be string, number, or array');
73        }
74
75        header_count = 1;
76        header_len = Buffer.byteLength(value + "", 'latin1');
77    }
78
79    this.removeHeader(key);
80
81    this.headers[key] = value;
82    this.headers_len += header_len + (header_key_len * header_count);
83    this.headers_count += header_count;
84};
85
86ServerResponse.prototype.getHeader = function getHeader(name) {
87    return this.headers[name];
88};
89
90ServerResponse.prototype.getHeaderNames = function getHeaderNames() {
91    return Object.keys(this.headers);
92};
93
94ServerResponse.prototype.getHeaders = function getHeaders() {
95    return this.headers;
96};
97
98ServerResponse.prototype.hasHeader = function hasHeader(name) {
99    return name in this.headers;
100};
101
102ServerResponse.prototype.removeHeader = function removeHeader(name) {
103    if (!(name in this.headers)) {
104        return;
105    }
106
107    let name_len = Buffer.byteLength(name + "", 'latin1');
108
109    if (Array.isArray(this.headers[name])) {
110        this.headers_count -= this.headers[name].length;
111        this.headers_len -= this.headers[name].length * name_len;
112
113        this.headers[name].forEach(function(val) {
114            this.headers_len -= Buffer.byteLength(val + "", 'latin1');
115        });
116
117    } else {
118        this.headers_count--;
119        this.headers_len -= name_len + Buffer.byteLength(this.headers[name] + "", 'latin1');
120    }
121
122    delete this.headers[name];
123};
124
125ServerResponse.prototype.sendDate = function sendDate() {
126    throw new Error("Not supported");
127};
128
129ServerResponse.prototype.setTimeout = function setTimeout(msecs, callback) {
130    this.timeout = msecs;
131
132    if (callback) {
133        this.on('timeout', callback);
134    }
135
136    return this;
137};
138
139// for Express
140ServerResponse.prototype._implicitHeader = function _implicitHeader() {
141    this.writeHead(this.statusCode);
142};
143
144ServerResponse.prototype.writeHead = writeHead;
145ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead;
146
147function writeHead(statusCode, reason, obj) {
148    var originalStatusCode = statusCode;
149
150    statusCode |= 0;
151
152    if (statusCode < 100 || statusCode > 999) {
153        throw new ERR_HTTP_INVALID_STATUS_CODE(originalStatusCode);
154    }
155
156    if (typeof reason === 'string') {
157        this.statusMessage = reason;
158
159    } else {
160        if (!this.statusMessage) {
161            this.statusMessage = http.STATUS_CODES[statusCode] || 'unknown';
162        }
163
164        obj = reason;
165    }
166
167    this.statusCode = statusCode;
168
169    if (obj) {
170        var k;
171        var keys = Object.keys(obj);
172
173        for (var i = 0; i < keys.length; i++) {
174            k = keys[i];
175
176            if (k) {
177                this.setHeader(k, obj[k]);
178            }
179        }
180    }
181
182    unit_lib.unit_response_headers(this, statusCode, this.headers, this.headers_count, this.headers_len);
183
184    this.headersSent = true;
185};
186
187ServerResponse.prototype._writeBody = function(chunk, encoding, callback) {
188    var contentLength = 0;
189
190    if (!this.headersSent) {
191        this.writeHead(this.statusCode);
192    }
193
194    if (this.finished) {
195        return this;
196    }
197
198    if (typeof chunk === 'function') {
199        callback = chunk;
200        chunk = null;
201
202    } else if (typeof encoding === 'function') {
203        callback = encoding;
204        encoding = null;
205    }
206
207    if (chunk) {
208        if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
209            throw new TypeError('First argument must be a string or Buffer');
210        }
211
212        if (typeof chunk === 'string') {
213            contentLength = Buffer.byteLength(chunk, encoding);
214
215        } else {
216            contentLength = chunk.length;
217        }
218
219        unit_lib.unit_response_write(this, chunk, contentLength);
220    }
221
222    if (typeof callback === 'function') {
223        callback(this);
224    }
225};
226
227ServerResponse.prototype.write = function write(chunk, encoding, callback) {
228    this._writeBody(chunk, encoding, callback);
229
230    return true;
231};
232
233ServerResponse.prototype.end = function end(chunk, encoding, callback) {
234    this._writeBody(chunk, encoding, callback);
235
236    this.finished = true;
237
238    return this;
239};
240
241function ServerRequest(server) {
242    EventEmitter.call(this);
243
244    this.server = server;
245}
246util.inherits(ServerRequest, EventEmitter);
247
248ServerRequest.prototype.unpipe = undefined;
249
250ServerRequest.prototype.setTimeout = function setTimeout(msecs, callback) {
251    this.timeout = msecs;
252
253    if (callback) {
254        this.on('timeout', callback);
255    }
256
257    return this;
258};
259
260ServerRequest.prototype.statusCode = function statusCode() {
261    /* Only valid for response obtained from http.ClientRequest. */
262};
263
264ServerRequest.prototype.statusMessage = function statusMessage() {
265    /* Only valid for response obtained from http.ClientRequest. */
266};
267
268ServerRequest.prototype.trailers = function trailers() {
269    throw new Error("Not supported");
270};
271
272ServerRequest.prototype.METHODS = function METHODS() {
273    return http.METHODS;
274};
275
276ServerRequest.prototype.STATUS_CODES = function STATUS_CODES() {
277    return http.STATUS_CODES;
278};
279
280ServerRequest.prototype.listeners = function listeners() {
281    return [];
282};
283
284ServerRequest.prototype.resume = function resume() {
285    return [];
286};
287
288function Server(requestListener) {
289    EventEmitter.call(this);
290
291    this.unit = new unit_lib.Unit();
292    this.unit.server = this;
293
294    this.unit.createServer();
295
296    this.socket = Socket;
297    this.request = ServerRequest;
298    this.response = ServerResponse;
299
300    if (requestListener) {
301        this.on('request', requestListener);
302    }
303}
304util.inherits(Server, EventEmitter);
305
306Server.prototype.setTimeout = function setTimeout(msecs, callback) {
307    this.timeout = msecs;
308
309    if (callback) {
310        this.on('timeout', callback);
311    }
312
313    return this;
314};
315
316Server.prototype.listen = function () {
317    this.unit.listen();
318};
319
320Server.prototype.run_events = function (server, req, res) {
321    /* Important!!! setImmediate starts the next iteration in Node.js loop. */
322    setImmediate(function () {
323        server.emit("request", req, res);
324
325        Promise.resolve().then(() => {
326            let buf = server.unit._read(req.socket.req_pointer);
327
328            if (buf.length != 0) {
329                req.emit("data", buf);
330            }
331
332            req.emit("end");
333        });
334
335        Promise.resolve().then(() => {
336            req.emit("finish");
337
338            if (res.finished) {
339                unit_lib.unit_response_end(res);
340            }
341        });
342    });
343};
344
345function connectionListener(socket) {
346}
347
348module.exports = {
349    STATUS_CODES: http.STATUS_CODES,
350    Server,
351    ServerResponse,
352    ServerRequest,
353    _connectionListener: connectionListener
354};
355