/*
* Copyright (C) NGINX, Inc.
*/
'use strict';
const EventEmitter = require('events');
const http = require('http');
const util = require('util');
const unit_lib = require('./build/Release/unit-http');
const Socket = require('./socket');
const WebSocketFrame = require('./websocket_frame');
function ServerResponse(req) {
EventEmitter.call(this);
this.headers = {};
this.server = req.server;
this._request = req;
req._response = this;
this.socket = req.socket;
this.connection = req.connection;
}
util.inherits(ServerResponse, EventEmitter);
ServerResponse.prototype.statusCode = 200;
ServerResponse.prototype.statusMessage = undefined;
ServerResponse.prototype.headers_len = 0;
ServerResponse.prototype.headers_count = 0;
ServerResponse.prototype.headersSent = false;
ServerResponse.prototype.finished = false;
ServerResponse.prototype._finish = function _finish() {
this.headers = {};
this.headers_len = 0;
this.headers_count = 0;
this.finished = true;
};
ServerResponse.prototype.assignSocket = function assignSocket(socket) {
};
ServerResponse.prototype.detachSocket = function detachSocket(socket) {
};
ServerResponse.prototype.writeContinue = function writeContinue(cb) {
};
ServerResponse.prototype.writeProcessing = function writeProcessing(cb) {
};
ServerResponse.prototype.setHeader = function setHeader(name, value) {
if (typeof name !== 'string') {
throw new TypeError('Name argument must be a string');
}
let value_len = 0
let count = 0;
if (Array.isArray(value)) {
count = value.length;
value.forEach(function(val) {
value_len += Buffer.byteLength(val + "", 'latin1');
});
} else {
count = 1;
value_len = Buffer.byteLength(value + "", 'latin1');
}
let lc_name = name.toLowerCase();
if (lc_name in this.headers) {
this._removeHeader(lc_name);
}
let name_len = Buffer.byteLength(name, 'latin1');
this.headers[lc_name] = [name, value];
this.headers_len += value_len + (name_len * count);
this.headers_count += count;
};
ServerResponse.prototype.getHeader = function getHeader(name) {
const entry = this.headers[name.toLowerCase()];
return entry && entry[1];
};
ServerResponse.prototype.getHeaderNames = function getHeaderNames() {
return Object.keys(this.headers);
};
ServerResponse.prototype.getHeaders = function getHeaders() {
const ret = Object.create(null);
if (this.headers) {
const keys = Object.keys(this.headers);
for (var i = 0; i < keys.length; i++) {
const key = keys[i];
ret[key] = this.headers[key][1];
}
}
return ret;
};
ServerResponse.prototype.hasHeader = function hasHeader(name) {
return name.toLowerCase() in this.headers;
};
ServerResponse.prototype.removeHeader = function removeHeader(name) {
if (typeof name !== 'string') {
throw new TypeError('Name argument must be a string');
}
let lc_name = name.toLowerCase();
if (lc_name in this.headers) {
this._removeHeader(lc_name);
}
};
ServerResponse.prototype._removeHeader = function _removeHeader(lc_name) {
let entry = this.headers[lc_name];
let name_len = Buffer.byteLength(entry[0] + "", 'latin1');
let value = entry[1];
delete this.headers[lc_name];
if (Array.isArray(value)) {
this.headers_count -= value.length;
this.headers_len -= value.length * name_len;
value.forEach(function(val) {
this.headers_len -= Buffer.byteLength(val + "", 'latin1');
});
return;
}
this.headers_count--;
this.headers_len -= name_len + Buffer.byteLength(value + "", 'latin1');
};
ServerResponse.prototype.sendDate = function sendDate() {
throw new Error("Not supported");
};
ServerResponse.prototype.setTimeout = function setTimeout(msecs, callback) {
this.timeout = msecs;
if (callback) {
this.on('timeout', callback);
}
return this;
};
ServerResponse.prototype.writeHead = writeHead;
ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead;
function writeHead(statusCode, reason, obj) {
var originalStatusCode = statusCode;
statusCode |= 0;
if (statusCode < 100 || statusCode > 999) {
throw new ERR_HTTP_INVALID_STATUS_CODE(originalStatusCode);
}
if (typeof reason === 'string') {
this.statusMessage = reason;
} else {
if (!this.statusMessage) {
this.statusMessage = http.STATUS_CODES[statusCode] || 'unknown';
}
obj = reason;
}
this.statusCode = statusCode;
if (obj) {
var k;
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
k = keys[i];
if (k) {
this.setHeader(k, obj[k]);
}
}
}
return this;
};
/*
* Some Node.js packages are known to be using this undocumented function,
* notably "compression" middleware.
*/
ServerResponse.prototype._implicitHeader = function _implicitHeader() {
this.writeHead(this.statusCode);
};
ServerResponse.prototype._send_headers = unit_lib.response_send_headers;
ServerResponse.prototype._sendHeaders = function _sendHeaders() {
if (!this.headersSent) {
this._send_headers(this.statusCode, this.headers, this.headers_count,
this.headers_len);
this.headersSent = true;
}
};
ServerResponse.prototype._write = unit_lib.response_write;
ServerResponse.prototype._writeBody = function(chunk, encoding, callback) {
var contentLength = 0;
this._sendHeaders();
if (typeof chunk === 'function') {
callback = chunk;
chunk = null;
} else if (typeof encoding === 'function') {
callback = encoding;
encoding = null;
}
if (chunk) {
if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) {
throw new TypeError('First argument must be a string or Buffer');
}
if (typeof chunk === 'string') {
contentLength = Buffer.byteLength(chunk, encoding);
} else {
contentLength = chunk.length;
}
this._write(chunk, contentLength);
}
if (typeof callback === 'function') {
/*
* The callback must be called only when response.write() caller
* completes. process.nextTick() postpones the callback execution.
*
* process.nextTick() is not technically part of the event loop.
* Instead, the nextTickQueue will be processed after the current
* operation completes, regardless of the current phase of
* the event loop. All callbacks passed to process.nextTick()
* will be resolved before the event loop continues.
*/
process.nextTick(function () {
callback(this);
}.bind(this));
}
};
ServerResponse.prototype.write = function write(chunk, encoding, callback) {
if (this.finished) {
throw new Error("Write after end");
}
this._writeBody(chunk, encoding, callback);
return true;
};
ServerResponse.prototype._end = unit_lib.response_end;
ServerResponse.prototype.end = function end(chunk, encoding, callback) {
if (!this.finished) {
this._writeBody(chunk, encoding, callback);
this._end();
this.finished = true;
}
return this;
};
function ServerRequest(server, socket) {
EventEmitter.call(this);
this.server = server;
this.socket = socket;
this.connection = socket;
}
util.inherits(ServerRequest, EventEmitter);
ServerRequest.prototype.unpipe = undefined;
ServerRequest.prototype.setTimeout = function setTimeout(msecs, callback) {
this.timeout = msecs;
if (callback) {
this.on('timeout', callback);
}
return this;
};
ServerRequest.prototype.statusCode = function statusCode() {
/* Only valid for response obtained from http.ClientRequest. */
};
ServerRequest.prototype.statusMessage = function statusMessage() {
/* Only valid for response obtained from http.ClientRequest. */
};
ServerRequest.prototype.trailers = function trailers() {
throw new Error("Not supported");
};
ServerRequest.prototype.METHODS = function METHODS() {
return http.METHODS;
};
ServerRequest.prototype.STATUS_CODES = function STATUS_CODES() {
return http.STATUS_CODES;
};
ServerRequest.prototype.listeners = function listeners() {
return [];
};
ServerRequest.prototype.resume = function resume() {
return [];
};
/*
* The "on" method is overridden to defer reading data until user code is
* ready, that is (ev === "data"). This can occur after req.emit("end") is
* executed, since the user code can be scheduled asynchronously by Promises
* and so on. Passing the data is postponed by process.nextTick() until
* the "on" method caller completes.
*/
ServerRequest.prototype.on = function on(ev, fn) {
Server.prototype.on.call(this, ev, fn);
if (ev === "data") {
process.nextTick(function () {
if (this._data.length !== 0) {
this.emit("data", this._data);
}
}.bind(this));
}
};
ServerRequest.prototype.addListener = ServerRequest.prototype.on;
function Server(requestListener) {
EventEmitter.call(this);
this.unit = new unit_lib.Unit();
this.unit.server = this;
this.unit.createServer();
this.Socket = Socket;
this.ServerRequest = ServerRequest;
this.ServerResponse = ServerResponse;
this.WebSocketFrame = WebSocketFrame;
if (requestListener) {
this.on('request', requestListener);
}
this._upgradeListenerCount = 0;
this.on('newListener', function(ev) {
if (ev === 'upgrade'){
this._upgradeListenerCount++;
}
}).on('removeListener', function(ev) {
if (ev === 'upgrade') {
this._upgradeListenerCount--;
}
});
}
util.inherits(Server, EventEmitter);
Server.prototype.setTimeout = function setTimeout(msecs, callback) {
this.timeout = msecs;
if (callback) {
this.on('timeout', callback);
}
return this;
};
Server.prototype.listen = function () {
this.unit.listen();
};
Server.prototype.emit_request = function (req, res) {
if (req._websocket_handshake && this._upgradeListenerCount > 0) {
this.emit('upgrade', req, req.socket);
} else {
this.emit("request", req, res);
}
process.nextTick(() => {
req.emit("finish");
req.emit("end");
});
};
Server.prototype.emit_close = function () {
this.emit('close');
};
function connectionListener(socket) {
}
module.exports = {
STATUS_CODES: http.STATUS_CODES,
Server,
ServerResponse,
ServerRequest,
_connectionListener: connectionListener
};