const EventEmitter = require('events');
class HttpDuplex extends EventEmitter {
/**
* Constructs a proxy object over input and output resulting in a unified stream.
* Generally meant to combine request and response streams in the http.request event
* @class HttpDuplex
* @param {http.IncomingMessage} input - client request object from request event
* @param {http.ServerResponse} output - response object from request event
* @requires events
* @extends EventEmitter
* @see {@link https://nodejs.org/api/http.html#http_event_request|request}
* @see {@link https://nodejs.org/api/http.html#http_class_http_incomingmessage|http.IncomingMessage}
* @see {@link https://nodejs.org/api/http.html#http_class_http_serverresponse|http.ServerResponse}
* @example <caption>A simple example is shown below</caption>
* http.createServer(function (req, res) {
* var dup = new HttpDuplex(req, res);
* res.end("Request: " + req.method + " " + req.url);
* }).listen(80);
*/
constructor(input, output) {
super();
/**
* A IncomingMessage created by http.Server or http.ClientRequest usually passed as the
* first parameter to the 'request' and 'response' events. Implements Readable Stream interface
* but may not be a decendant thereof.
* @type {http.IncomingMessage}
* @see {@link https://nodejs.org/api/http.html#http_event_request|request}
* @see {@link https://nodejs.org/api/http.html#http_class_http_incomingmessage|http.IncomingMessage}
*
*/
this.req = input;
/**
* Created http.server. Passed as the second parameter to the 'request' event.
* The response implements Writable Stream interface but isn't a descendent thereof.
* @type {http.ServerResponse}
* @see {@link https://nodejs.org/api/http.html#http_event_request|request}
* @see {@link https://nodejs.org/api/http.html#http_class_http_serverresponse|http.ServerResponse}
*/
this.res = output;
var self = this;
// request / input proxy events
['data', 'end', 'error', 'close'].forEach(function (name) {
self.req.on(name, self.emit.bind(self, name));
});
// respone / output proxy events
['error', 'drain'].forEach(function (name) {
self.res.on(name, self.emit.bind(self, name));
});
}
// input / request wrapping
get client() {
return this.req.client;
}
get complete() {
return this.req.complete;
}
/**
* Reference to the underlying socket for the request connection.
* @type {net.Socket}
* @readonly
* @see {@link https://nodejs.org/api/http.html#http_request_socket|request.Socket}
*/
get connection() {
return this.req.connection;
}
/**
* Request/response headers. Key-value pairs of header names and values. Header names are always lower-case.
* @name headers
* @alias HttpDuplex.headers
* @memberof HttpDuplex
* @type {Object}
* @readonly
* @see {@link https://nodejs.org/api/http.html#http_message_headers|message.headers}
*/
get headers() {
return this.req.headers;
}
/**
* Requested HTTP Version sent by the client. Usually either '1.0' or '1.1'
* @name httpVersion
* @alias HttpDuplex.httpVersion
* @memberof HttpDuplex
* @type {String}
* @see {@link https://nodejs.org/api/http.html#http_message_httpversion|message.httpVersion}
* @readonly
*/
get httpVersion() {
return this.req.httpVersion;
}
/**
* First integer in the httpVersion string
* @name httpVersionMajor
* @alias HttpDuplex.httpVersionMajor
* @memberof HttpDuplex
* @type {Number}
* @see httpVersion
* @readonly
*/
get httpVersionMajor() {
return this.req.httpVersionMajor;
}
/**
* Second integer ni the httpVersion string
* @name httpVersionMinor
* @alias HttpDuplex.httpVersionMinor
* @memberof HttpDuplex
* @type {String}
* @see httpVersion
* @readonly
*/
get httpVersionMinor() {
return this.req.httpVersionMinor;
}
/**
* Request method of the incoming request.
* @type {String}
* @see {@link https://nodejs.org/api/http.html#http_event_request|request}
* @see {@link https://nodejs.org/api/http.html#http_class_http_serverresponse|http.ServerResponse}
* @example 'GET', 'DELETE'
* @readonly
*/
get method() {
return this.req.method;
}
/**
* Is this stream readable.
* @type {Boolean}
* @readonly
*/
get readable() {
return this.req.readable;
}
/**
* net.Socket object associated with the connection.
* @type net.Socket
* @see {@link https://nodejs.org/api/net.html#net_class_net_socket|net.Socket}
* @readonly
*/
get socket() {
return this.req.socket;
}
/**
* The HTTP status code. Generally assigned before sending headers for a response to a client.
* @type {Number}
* @default 200
* @see {@link https://nodejs.org/api/http.html#http_response_statuscode|response.statusCode}
* @example request.statusCode = 404;
*/
get statusCode() {
return this.res.statusCode;
}
set statusCode(val) {
this.res.statusCode = val;
}
/**
* Controls the status message sent to the client as long as an explicit call to response.writeHead() isn't made
* If ignored or the value is undefined, the default message corresponding to the status code will be used.
* @type {String}
* @default undefined
* @see {@link https://nodejs.org/api/http.html#http_response_statusmessage|response.statusMessage}
* @example request.statusMessage = 'Document Not found';
*/
get statusMessage() {
return this.res.statusMessage;
}
set statusMessage(val) {
this.res.statusMessage = val;
}
/**
* Request/response trailer headers. Just like {@link headers} except these are only written
* after the initial response to the client.
* This object is only populated at the 'end' event and only work if a 'transfer-encoding: chunked'
* header is sent in the initial response.
* @name HttpDuplex#trailers
* @type {Object}
* @readonly
* @see headers
* @see addTrailers
* @see {@link https://nodejs.org/api/http.html#http_message_trailers|message.trailers}
* @see {@link https://nodejs.org/api/http.html#http_response_addtrailers_headers|response.addTrailers}
*/
get trailers() {
return this.req.trailers;
}
/**
* Whether or not the client connection has been upgraded
* @type {Boolean}
* @see {@link https://nodejs.org/api/http.html#http_event_upgrade_1|upgrade}
* @readonly
*/
get upgrade() {
return this.req.upgrade;
}
/**
* Request URL string.
* @example <caption>A request made as:</caption>
* GET /info?check=none HTTP/1.1
* @example <caption>Will return the string</caption>
* '/info?check=none'
* @type {String}
* @readonly
*/
get url() {
return this.req.url;
}
// output / response wrapping
get writable() {
return this.res.writable;
}
/**
* Sends a response header to the client request. Must only be called one time and before calling response.end().
* @method writeHead
* @alias HttpDuplex.writeHead
* @memberof HttpDuplex
* @param {number} statusCode 3-digit HTTP status code, like 404
* @param {string} [statusMessage] An optional human readable status message to send with the status code
* @param {object} [headers] An object containing the response headers to send
* @returns {this}
* @see {@link https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers|response.writeHead}
* @example var content = 'Under Construction...';
* response.writeHead(200, {
* 'Content-Length': Buffer.byteLength(content),
* 'Content-Type': 'text/plain'
* });
* response.end(content);
*/
writeHead(statusCode, statusMessage, headers) {
this.res.writeHead(statusCode, statusMessage, headers);
return this;
}
/**
* Buffers written data in memory. This data will be flushed when either the uncork or end methods are called.
* @method cork
* @alias HttpDuplex.cork
* @memberof HttpDuplex
* @returns {this}
* @see uncork
* @see {@link https://nodejs.org/api/stream.html#stream_writable_cork|stream.Writeable.cork}
* @example
* request.cork();
* request.write('buffer data ');
* request.write('before sending ');
* request.uncork();
*/
cork() {
this.res.connection.cork();
return this;
}
/**
* Flushes all data buffered since cork() was called.
* @method uncork
* @alias HttpDuplex.cork
* @memberof HttpDuplex
* @returns {this}
* @see cork
* @see {@link https://nodejs.org/api/stream.html#stream_writable_uncork|stream.Writeable.uncork}
*/
uncork() {
this.res.connection.uncork();
return this;
}
}
// proxy request methods
['pause', 'resume', 'setEncoding'].forEach(function (name) {
HttpDuplex.prototype[name] = function () {
return this.req[name].apply(this.req, Array.from(arguments));
};
});
// proxy respone methods
[
'setDefaultEncoding', 'write', 'end', 'flush', 'writeHeader', 'writeContinue',
'setHeader', 'getHeader', 'removeHeader', 'addTrailers'
].forEach(function (name) {
HttpDuplex.prototype[name] = function () {
return this.res[name].apply(this.res, Array.from(arguments));
};
});
/**
* Destroys object and it's bound streams
* @method destroy
* @alias HttpDuplex.destroy
* @memberof HttpDuplex
*/
HttpDuplex.prototype.destroy = function () {
this.req.destroy();
this.res.destroy();
};
module.exports = HttpDuplex;
/**
* Event emitted when the underlying request connection is closed. This only occurs once per response.
* @event close
* @alias HttpDuplex#event:close
* @memberof HttpDuplex
* @see end
* @see {@link https://nodejs.org/api/http.html#http_event_close_2|http.IncomingMessage/close}
*/
/**
* This event is emitted when data on the stream can be consumed. This may occur whenever the stream is switched into
* flowing mode by calling readable.pipe() or readable.resume() or by attaching a listener this event.<p/>
* This event is emitted when readable.read() is called and a chunk of data becomes available.
* Data will be passed as a string if the default encoding has been set using readable.setEncoding(); otherwise it's
* passed as a Buffer.
* @event data
* @alias HttpDuplex#event:data
* @param {(string|buffer|any)} chunk The chunk is either a buffer or string when the stream isn't operating
* in object mode. When the stream is in object mode, the chunk can be any JavaScript value other than null.
* @memberof HttpDuplex
* @see {@link https://nodejs.org/api/stream.html#stream_event_data|stream.Readable/data}
*/
/**
* If a call to response.write(chunk) returns false, the drain event will be emitted once it is appropriate to
* resume writing data to the stream.
* @event drain
* @alias HttpDuplex#event:drain
* @memberof HttpDuplex
* @see {@link https://nodejs.org/api/stream.html#stream_event_drain|stream.Writable/drain}
*/
/**
* This event is emitted once no more consumable data is left on the readable stream.<p/>
* *Note*: This is only emitted when all data is completely consumed.
* @event end
* @alias HttpDuplex#event:end
* @memberof HttpDuplex
* @see {@link https://nodejs.org/api/stream.html#stream_event_end|stream.Readable/end}
*/
/**
* This event may be emitted one of the underlying Readable or Writable stream implementations at any time.
* This may happen in the following cases:
* + if the underlying streams are unable to produce data because of an internal failure
* + if an attempt is made to push an invalid chunk of data.
* + if an error occurred while writing or piping data.<p/>
*
* The listener callback will be passed a single Error object.<br/>
* *Note*: Streams are not closed when the event is emitted.
* @event error
* @alias HttpDuplex#event:error
* @memberof HttpDuplex
* @see {@link https://nodejs.org/api/stream.html#stream_event_error_1|stream.Readable/error}
* @see {@link https://nodejs.org/api/stream.html#stream_event_error|stream.Writeable/error}
*/
/**
* Adds trailing headers to the response.
* Trailers will only be emitted if chunked encoding is enabled for the response; otherwise they are discarded.
* @method addTrailers
* @name addTrailers
* @alias HttpDuplex.addTrailers
* @memberof HttpDuplex
* @param {Object} headers Trailing headers to add to the response
* @see trailers
* @see headers
* @see {@link https://nodejs.org/api/http.html#http_message_trailers|message.trailers}
* @see {@link https://nodejs.org/api/http.html#http_response_addtrailers_headers|response.addTrailers}
*/
/**
* Tells the server the response headers and body have been sent and that the message should be considered complete.
* This MUST be called on every response.
* If data is specified, this behaves as if calling response.write(data, encoding) followed by response.end(callback).
* If specified, the callback is called once the response stream is finished.
* @method end
* @alias HttpDuplex.end
* @memberof HttpDuplex
* @param {(string|Buffer)} data optional data to write to the response before closing the connection
* @param {String} encoding Encoding that should be used to write the data
* @param {function} callback Function to be called once the response stream is finished
* @see {@link https://nodejs.org/api/http.html#http_response_end_data_encoding_callback|response.end}
*/
/**
* Returns the current value of a header; name is case insensitive.
* @method getHeader
* @alias HttpDuplex.getHeader
* @memberof HttpDuplex
* @param {String} name Header to get the value of
* @returns {String}
* @see {@link https://nodejs.org/api/http.html#http_request_getheader_name|getHeader}
* @example
* let contentType = request.getHeader('Content-Type');
*/
/**
* Switch readable stream out of flowing mode and stop emitting 'data' events.
* Any new data that becomes available during this time will stay buffered until resume is called.
* @method pause
* @alias HttpDuplex.pause
* @memberof HttpDuplex
* @see {@link https://nodejs.org/api/stream.html#stream_readable_pause|stream.Readable.pause}
*/
/**
* Remove a header from the response headers.
* @method removeHeader
* @alias HttpDuplex.removeHeader
* @memberof HttpDuplex
* @param {String} name Header to remove
* @see {@link https://nodejs.org/api/http.html#http_request_removeheader_name|removeHeader}
* @example
* request.removeHeader('Content-Type');
*/
/**
* Switch readable stream back into flowing mode and restart emitting 'data' events.
* This can be used to consume all data waiting without processing any of it.
* @method resume
* @alias HttpDuplex.resume
* @memberof HttpDuplex
* @see {@link https://nodejs.org/api/stream.html#stream_readable_resume|stream.Readable.resume}
*/
/**
* Sets the character encoding for data written to the stream.
* @method setDefaultEncoding
* @alias HttpDuplex.setDefaultEncoding
* @memberof HttpDuplex
* @param encoding {String} new character encoding
* @see setEncoding
* @example request.setDefaultEncoding('utf8');
*/
/**
* Sets the character encoding for data read from the stream.
* @method setEncoding
* @alias HttpDuplex.setEncoding
* @memberof HttpDuplex
* @param encoding {String} new character encoding
* @see setDefaultEncoding
* @example request.setEncoding('utf8');
*/
/**
* Set a single header. If the header already exists, it will be replaced.
* It's possible to use an array of strings in place of value to send multiple headers with the same name.
* @method setHeader
* @alias HttpDuplex.setHeader
* @memberof HttpDuplex
* @param {String} name Header to set
* @param {string|string[]} value Value(s) to assign to header
* @see removeHeader
* @see {@link https://nodejs.org/api/http.html#http_request_setheader_name_value|setHeader}
* @example <caption>Single value</caption>
* request.setHeader('Content-Type', 'text/html');
* @example <caption>Array of string value</caption>
* request.setHeader('Set-Cookie', ['type=auth', 'language=javascript']);
*/
/**
* Sends a chunk of the response body. This method may be called multiple times to provide successive parts of the
* body.
* <p>*Note:* If write() is called either before writeHead() or writeHead() just hasn't been called, it will switch * modes and flush the implicit headers that may be waiting before parts of this chunk are sent.<p/>
* Node will buffer up to the first chunk of the body. Any additional calls to write() may be buffered as well
* for packet efficiency purposes.</p>
* Returns true if the entire data was flushed successfully to the kernel buffer. Returns false if all or part of
* the data was buffered in memory.
* @method write
* @alias HttpDuplex.write
* @memberof HttpDuplex
* @param {(string|Buffer)} chunk chunk of data to send.
* @param {String} [encoding='utf8'] If chunk is a string, this specifies how to encode it into a byte stream.
* @param {function} [callback] Callback to call when this chunk of data is flushed.
* @returns {Boolean}
* @emits {@link event:drain|drain} Emitted when data was buffered and the buffer has become free for use again.
* @see {@link https://nodejs.org/api/http.html#http_response_write_chunk_encoding_callback|http.ServerResponse.write}
*/
/**
* Sends an HTTP/1.1 100 Continue message to the client.
* @method writeContinue
* @alias HttpDuplex.writeContinue
* @memberof HttpDuplex
* @see {@link https://nodejs.org/api/http.html#http_response_writecontinue|response.writeContinue}
* {@link https://nodejs.org/api/http.html#http_event_checkcontinue|http.Server/checkContinue}
*/
/**
* __Warning:__ This has been deprecated in node, __don't__ use it. Any apis that require this funtion should be
* updated to use writeHead insted.
* @method writeHeader
* @alias HttpDuplex.writeHeader
* @memberof HttpDuplex
* @deprecated {@link https://nodejs.org/api/deprecations.html#deprecations_dep0063_serverresponse_prototype_writeheader|Node Deprecated}
* @see writeHead
*/