From c247cb02a16d16759e2abb92e3484a5c9ff97a3a Mon Sep 17 00:00:00 2001 From: Peter Marton Date: Thu, 19 Oct 2017 20:16:02 +0200 Subject: [PATCH] http: add options to http.createServer() This adds the optional options argument to `http.createServer()`. It contains two options: the `IncomingMessage` and `ServerReponse` option. PR-URL: https://github.com/nodejs/node/pull/15752 Reviewed-By: Matteo Collina Reviewed-By: Anatoli Papirovski Reviewed-By: James M Snell Reviewed-By: Evan Lucas --- doc/api/http.md | 14 ++++- doc/api/https.md | 4 +- lib/_http_common.js | 10 +++- lib/_http_server.js | 23 +++++++-- lib/https.js | 6 +++ ...st-http-server-options-incoming-message.js | 41 +++++++++++++++ ...est-http-server-options-server-response.js | 35 +++++++++++++ ...t-https-server-options-incoming-message.js | 51 +++++++++++++++++++ ...st-https-server-options-server-response.js | 47 +++++++++++++++++ 9 files changed, 222 insertions(+), 9 deletions(-) create mode 100644 test/parallel/test-http-server-options-incoming-message.js create mode 100644 test/parallel/test-http-server-options-server-response.js create mode 100644 test/parallel/test-https-server-options-incoming-message.js create mode 100644 test/parallel/test-https-server-options-server-response.js diff --git a/doc/api/http.md b/doc/api/http.md index 58e5b53eaf41d3..1b15247cb7d5f5 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -1663,10 +1663,20 @@ A collection of all the standard HTTP response status codes, and the short description of each. For example, `http.STATUS_CODES[404] === 'Not Found'`. -## http.createServer([requestListener]) +## http.createServer([options][, requestListener]) + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/15752 + description: The `options` argument is supported now. +--> +- `options` {Object} + * `IncomingMessage` {http.IncomingMessage} Specifies the IncomingMessage class + to be used. Useful for extending the original `IncomingMessage`. Defaults + to: `IncomingMessage` + * `ServerResponse` {http.ServerResponse} Specifies the ServerResponse class to + be used. Useful for extending the original `ServerResponse`. Defaults to: + `ServerResponse` - `requestListener` {Function} * Returns: {http.Server} diff --git a/doc/api/https.md b/doc/api/https.md index cb22873a01e1ba..29f5d35ab87233 100644 --- a/doc/api/https.md +++ b/doc/api/https.md @@ -65,7 +65,8 @@ See [`http.Server#keepAliveTimeout`][]. -- `options` {Object} Accepts `options` from [`tls.createServer()`][] and [`tls.createSecureContext()`][]. +- `options` {Object} Accepts `options` from [`tls.createServer()`][], + [`tls.createSecureContext()`][] and [`http.createServer()`][]. - `requestListener` {Function} A listener to be added to the `request` event. Example: @@ -258,6 +259,7 @@ const req = https.request(options, (res) => { [`http.Server#setTimeout()`]: http.html#http_server_settimeout_msecs_callback [`http.Server#timeout`]: http.html#http_server_timeout [`http.Server`]: http.html#http_class_http_server +[`http.createServer()`]: http.html#httpcreateserveroptions-requestlistener [`http.close()`]: http.html#http_server_close_callback [`http.get()`]: http.html#http_http_get_options_callback [`http.request()`]: http.html#http_http_request_options_callback diff --git a/lib/_http_common.js b/lib/_http_common.js index b4caf5939e5afc..a7e8b0c59b8854 100644 --- a/lib/_http_common.js +++ b/lib/_http_common.js @@ -34,6 +34,7 @@ const { const debug = require('util').debuglog('http'); +const kIncomingMessage = Symbol('IncomingMessage'); const kOnHeaders = HTTPParser.kOnHeaders | 0; const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0; const kOnBody = HTTPParser.kOnBody | 0; @@ -73,7 +74,11 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, parser._url = ''; } - parser.incoming = new IncomingMessage(parser.socket); + // Parser is also used by http client + var ParserIncomingMessage = parser.socket && parser.socket.server ? + parser.socket.server[kIncomingMessage] : IncomingMessage; + + parser.incoming = new ParserIncomingMessage(parser.socket); parser.incoming.httpVersionMajor = versionMajor; parser.incoming.httpVersionMinor = versionMinor; parser.incoming.httpVersion = `${versionMajor}.${versionMinor}`; @@ -300,5 +305,6 @@ module.exports = { freeParser, httpSocketSetup, methods, - parsers + parsers, + kIncomingMessage }; diff --git a/lib/_http_server.js b/lib/_http_server.js index c60119822a98d5..9541993df53321 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -33,6 +33,7 @@ const { continueExpression, chunkExpression, httpSocketSetup, + kIncomingMessage, _checkInvalidHeaderChar: checkInvalidHeaderChar } = require('_http_common'); const { OutgoingMessage } = require('_http_outgoing'); @@ -41,9 +42,12 @@ const { defaultTriggerAsyncIdScope, getOrSetAsyncId } = require('internal/async_hooks'); +const { IncomingMessage } = require('_http_incoming'); const errors = require('internal/errors'); const Buffer = require('buffer').Buffer; +const kServerResponse = Symbol('ServerResponse'); + const STATUS_CODES = { 100: 'Continue', 101: 'Switching Protocols', @@ -263,9 +267,19 @@ function writeHead(statusCode, reason, obj) { // Docs-only deprecated: DEP0063 ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead; +function Server(options, requestListener) { + if (!(this instanceof Server)) return new Server(options, requestListener); + + if (typeof options === 'function') { + requestListener = options; + options = {}; + } else if (options == null || typeof options === 'object') { + options = util._extend({}, options); + } + + this[kIncomingMessage] = options.IncomingMessage || IncomingMessage; + this[kServerResponse] = options.ServerResponse || ServerResponse; -function Server(requestListener) { - if (!(this instanceof Server)) return new Server(requestListener); net.Server.call(this, { allowHalfOpen: true }); if (requestListener) { @@ -587,7 +601,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { } } - var res = new ServerResponse(req); + var res = new server[kServerResponse](req); res._onPendingData = updateOutgoingData.bind(undefined, socket, state); res.shouldKeepAlive = keepAlive; @@ -690,5 +704,6 @@ module.exports = { STATUS_CODES, Server, ServerResponse, - _connectionListener: connectionListener + _connectionListener: connectionListener, + kServerResponse }; diff --git a/lib/https.js b/lib/https.js index 5013791fe2de25..741ce84d2f8820 100644 --- a/lib/https.js +++ b/lib/https.js @@ -36,6 +36,9 @@ const { inherits } = util; const debug = util.debuglog('https'); const { urlToOptions, searchParamsSymbol } = require('internal/url'); const errors = require('internal/errors'); +const { IncomingMessage, ServerResponse } = require('http'); +const { kIncomingMessage } = require('_http_common'); +const { kServerResponse } = require('_http_server'); function Server(opts, requestListener) { if (!(this instanceof Server)) return new Server(opts, requestListener); @@ -57,6 +60,9 @@ function Server(opts, requestListener) { opts.ALPNProtocols = ['http/1.1']; } + this[kIncomingMessage] = opts.IncomingMessage || IncomingMessage; + this[kServerResponse] = opts.ServerResponse || ServerResponse; + tls.Server.call(this, opts, _connectionListener); this.httpAllowHalfOpen = false; diff --git a/test/parallel/test-http-server-options-incoming-message.js b/test/parallel/test-http-server-options-incoming-message.js new file mode 100644 index 00000000000000..a4bfa1b7646fc6 --- /dev/null +++ b/test/parallel/test-http-server-options-incoming-message.js @@ -0,0 +1,41 @@ +'use strict'; + +/** + * This test covers http.Server({ IncomingMessage }) option: + * With IncomingMessage option the server should use + * the new class for creating req Object instead of the default + * http.IncomingMessage. + */ +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +class MyIncomingMessage extends http.IncomingMessage { + getUserAgent() { + return this.headers['user-agent'] || 'unknown'; + } +} + +const server = http.Server({ + IncomingMessage: MyIncomingMessage +}, common.mustCall(function(req, res) { + assert.strictEqual(req.getUserAgent(), 'node-test'); + res.statusCode = 200; + res.end(); +})); +server.listen(); + +server.on('listening', function makeRequest() { + http.get({ + port: this.address().port, + headers: { + 'User-Agent': 'node-test' + } + }, (res) => { + assert.strictEqual(res.statusCode, 200); + res.on('end', () => { + server.close(); + }); + res.resume(); + }); +}); diff --git a/test/parallel/test-http-server-options-server-response.js b/test/parallel/test-http-server-options-server-response.js new file mode 100644 index 00000000000000..f5adf39bed6d16 --- /dev/null +++ b/test/parallel/test-http-server-options-server-response.js @@ -0,0 +1,35 @@ +'use strict'; + +/** + * This test covers http.Server({ ServerResponse }) option: + * With ServerResponse option the server should use + * the new class for creating res Object instead of the default + * http.ServerResponse. + */ +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +class MyServerResponse extends http.ServerResponse { + status(code) { + return this.writeHead(code, { 'Content-Type': 'text/plain' }); + } +} + +const server = http.Server({ + ServerResponse: MyServerResponse +}, common.mustCall(function(req, res) { + res.status(200); + res.end(); +})); +server.listen(); + +server.on('listening', function makeRequest() { + http.get({ port: this.address().port }, (res) => { + assert.strictEqual(res.statusCode, 200); + res.on('end', () => { + server.close(); + }); + res.resume(); + }); +}); diff --git a/test/parallel/test-https-server-options-incoming-message.js b/test/parallel/test-https-server-options-incoming-message.js new file mode 100644 index 00000000000000..102ee56751b800 --- /dev/null +++ b/test/parallel/test-https-server-options-incoming-message.js @@ -0,0 +1,51 @@ +'use strict'; + +/** + * This test covers http.Server({ IncomingMessage }) option: + * With IncomingMessage option the server should use + * the new class for creating req Object instead of the default + * http.IncomingMessage. + */ +const common = require('../common'); +const fixtures = require('../common/fixtures'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const http = require('http'); +const https = require('https'); + +class MyIncomingMessage extends http.IncomingMessage { + getUserAgent() { + return this.headers['user-agent'] || 'unknown'; + } +} + +const server = https.createServer({ + key: fixtures.readKey('agent1-key.pem'), + cert: fixtures.readKey('agent1-cert.pem'), + ca: fixtures.readKey('ca1-cert.pem'), + IncomingMessage: MyIncomingMessage +}, common.mustCall(function(req, res) { + assert.strictEqual(req.getUserAgent(), 'node-test'); + res.statusCode = 200; + res.end(); +})); +server.listen(); + +server.on('listening', function makeRequest() { + https.get({ + port: this.address().port, + rejectUnauthorized: false, + headers: { + 'User-Agent': 'node-test' + } + }, (res) => { + assert.strictEqual(res.statusCode, 200); + res.on('end', () => { + server.close(); + }); + res.resume(); + }); +}); diff --git a/test/parallel/test-https-server-options-server-response.js b/test/parallel/test-https-server-options-server-response.js new file mode 100644 index 00000000000000..8745415f8b6596 --- /dev/null +++ b/test/parallel/test-https-server-options-server-response.js @@ -0,0 +1,47 @@ +'use strict'; + +/** + * This test covers http.Server({ ServerResponse }) option: + * With ServerResponse option the server should use + * the new class for creating res Object instead of the default + * http.ServerResponse. + */ +const common = require('../common'); +const fixtures = require('../common/fixtures'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const http = require('http'); +const https = require('https'); + +class MyServerResponse extends http.ServerResponse { + status(code) { + return this.writeHead(code, { 'Content-Type': 'text/plain' }); + } +} + +const server = https.createServer({ + key: fixtures.readKey('agent1-key.pem'), + cert: fixtures.readKey('agent1-cert.pem'), + ca: fixtures.readKey('ca1-cert.pem'), + ServerResponse: MyServerResponse +}, common.mustCall(function(req, res) { + res.status(200); + res.end(); +})); +server.listen(); + +server.on('listening', function makeRequest() { + https.get({ + port: this.address().port, + rejectUnauthorized: false + }, (res) => { + assert.strictEqual(res.statusCode, 200); + res.on('end', () => { + server.close(); + }); + res.resume(); + }); +});