| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 64fc625 commit 4395fe1
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3555,6 +3555,9 @@ Found'`. | |||
| 3555 | 3555 | <!-- YAML | |
| 3556 | 3556 | added: v0.1.13 | |
| 3557 | 3557 | changes: | |
| 3558 | + - version: REPLACEME | ||
| 3559 | + pr-url: https://github.com/nodejs/node/pull/59778 | ||
| 3560 | + description: Add optimizeEmptyRequests option. | ||
| 3558 | 3561 | - version: v24.9.0 | |
| 3559 | 3562 | pr-url: https://github.com/nodejs/node/pull/59824 | |
| 3560 | 3563 | description: The `shouldUpgradeCallback` option is now supported. | |
@@ -3660,6 +3663,11 @@ changes: | |||
| 3660 | 3663 | * `rejectNonStandardBodyWrites` {boolean} If set to `true`, an error is thrown | |
| 3661 | 3664 | when writing to an HTTP response which does not have a body. | |
| 3662 | 3665 | **Default:** `false`. | |
| 3666 | + * `optimizeEmptyRequests` {boolean} If set to `true`, requests without `Content-Length` | ||
| 3667 | + or `Transfer-Encoding` headers (indicating no body) will be initialized with an | ||
| 3668 | + already-ended body stream, so they will never emit any stream events | ||
| 3669 | + (like `'data'` or `'end'`). You can use `req.readableEnded` to detect this case. | ||
| 3670 | + **Default:** `false`. | ||
| 3663 | 3671 | ||
| 3664 | 3672 | * `requestListener` {Function} | |
| 3665 | 3673 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -423,6 +423,15 @@ function _addHeaderLineDistinct(field, value, dest) { | |||
| 423 | 423 | } | |
| 424 | 424 | } | |
| 425 | 425 | ||
| 426 | + IncomingMessage.prototype._dumpAndCloseReadable = function _dumpAndCloseReadable() { | ||
| 427 | + this._dumped = true; | ||
| 428 | + this._readableState.ended = true; | ||
| 429 | + this._readableState.endEmitted = true; | ||
| 430 | + this._readableState.destroyed = true; | ||
| 431 | + this._readableState.closed = true; | ||
| 432 | + this._readableState.closeEmitted = true; | ||
| 433 | + }; | ||
| 434 | + | ||
| 426 | 435 | ||
| 427 | 436 | // Call this instead of resume() if we want to just | |
| 428 | 437 | // dump all the data to /dev/null | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -107,6 +107,8 @@ const onResponseFinishChannel = dc.channel('http.server.response.finish'); | |||
| 107 | 107 | const kServerResponse = Symbol('ServerResponse'); | |
| 108 | 108 | const kServerResponseStatistics = Symbol('ServerResponseStatistics'); | |
| 109 | 109 | ||
| 110 | + const kOptimizeEmptyRequests = Symbol('OptimizeEmptyRequestsOption'); | ||
| 111 | + | ||
| 110 | 112 | const { | |
| 111 | 113 | hasObserver, | |
| 112 | 114 | startPerf, | |
@@ -455,6 +457,11 @@ function storeHTTPOptions(options) { | |||
| 455 | 457 | validateInteger(maxHeaderSize, 'maxHeaderSize', 0); | |
| 456 | 458 | this.maxHeaderSize = maxHeaderSize; | |
| 457 | 459 | ||
| 460 | + const optimizeEmptyRequests = options.optimizeEmptyRequests; | ||
| 461 | + if (optimizeEmptyRequests !== undefined) | ||
| 462 | + validateBoolean(optimizeEmptyRequests, 'options.optimizeEmptyRequests'); | ||
| 463 | + this[kOptimizeEmptyRequests] = optimizeEmptyRequests || false; | ||
| 464 | + | ||
| 458 | 465 | const insecureHTTPParser = options.insecureHTTPParser; | |
| 459 | 466 | if (insecureHTTPParser !== undefined) | |
| 460 | 467 | validateBoolean(insecureHTTPParser, 'options.insecureHTTPParser'); | |
@@ -1069,6 +1076,10 @@ function emitCloseNT(self) { | |||
| 1069 | 1076 | } | |
| 1070 | 1077 | } | |
| 1071 | 1078 | ||
| 1079 | + function hasBodyHeaders(headers) { | ||
| 1080 | + return ('content-length' in headers) || ('transfer-encoding' in headers); | ||
| 1081 | + } | ||
| 1082 | + | ||
| 1072 | 1083 | // The following callback is issued after the headers have been read on a | |
| 1073 | 1084 | // new message. In this callback we setup the response object and pass it | |
| 1074 | 1085 | // to the user. | |
@@ -1120,6 +1131,19 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { | |||
| 1120 | 1131 | }); | |
| 1121 | 1132 | } | |
| 1122 | 1133 | ||
| 1134 | + // Check if we should optimize empty requests (those without Content-Length or Transfer-Encoding headers) | ||
| 1135 | + const shouldOptimize = server[kOptimizeEmptyRequests] === true && !hasBodyHeaders(req.headers); | ||
| 1136 | + | ||
| 1137 | + if (shouldOptimize) { | ||
| 1138 | + // Fast processing where emitting 'data', 'end' and 'close' events is | ||
| 1139 | + // skipped and data is dumped. | ||
| 1140 | + // This avoids a lot of unnecessary overhead otherwise introduced by | ||
| 1141 | + // stream.Readable life cycle rules. The downside is that this will | ||
| 1142 | + // break some servers that read bodies for methods that don't have body headers. | ||
| 1143 | + req._dumpAndCloseReadable(); | ||
| 1144 | + req._read(); | ||
| 1145 | + } | ||
| 1146 | + | ||
| 1123 | 1147 | if (socket._httpMessage) { | |
| 1124 | 1148 | // There are already pending outgoing res, append. | |
| 1125 | 1149 | state.outgoing.push(res); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,77 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../common'); | ||
| 4 | + const assert = require('assert'); | ||
| 5 | + const http = require('http'); | ||
| 6 | + const net = require('net'); | ||
| 7 | + | ||
| 8 | + let reqs = 0; | ||
| 9 | + let optimizedReqs = 0; | ||
| 10 | + const server = http.createServer({ | ||
| 11 | + optimizeEmptyRequests: true | ||
| 12 | + }, (req, res) => { | ||
| 13 | + reqs++; | ||
| 14 | + if (req._dumped) { | ||
| 15 | + optimizedReqs++; | ||
| 16 | + req.on('data', common.mustNotCall()); | ||
| 17 | + req.on('end', common.mustNotCall()); | ||
| 18 | + | ||
| 19 | + assert.strictEqual(req._dumped, true); | ||
| 20 | + assert.strictEqual(req.readableEnded, true); | ||
| 21 | + assert.strictEqual(req.destroyed, true); | ||
| 22 | + } | ||
| 23 | + res.writeHead(200); | ||
| 24 | + res.end('ok'); | ||
| 25 | + }); | ||
| 26 | + | ||
| 27 | + server.listen(0, common.mustCall(async () => { | ||
| 28 | + // GET request without Content-Length (should be optimized) | ||
| 29 | + const getRequest = 'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n'; | ||
| 30 | + await makeRequest(getRequest); | ||
| 31 | + | ||
| 32 | + // HEAD request (should always be optimized regardless of headers) | ||
| 33 | + const headRequest = 'HEAD / HTTP/1.1\r\nHost: localhost\r\n\r\n'; | ||
| 34 | + await makeRequest(headRequest); | ||
| 35 | + | ||
| 36 | + // POST request without body headers (should be optimized) | ||
| 37 | + const postWithoutBodyHeaders = 'POST / HTTP/1.1\r\nHost: localhost\r\n\r\n'; | ||
| 38 | + await makeRequest(postWithoutBodyHeaders); | ||
| 39 | + | ||
| 40 | + // DELETE request without body headers (should be optimized) | ||
| 41 | + const deleteWithoutBodyHeaders = 'DELETE / HTTP/1.1\r\nHost: localhost\r\n\r\n'; | ||
| 42 | + await makeRequest(deleteWithoutBodyHeaders); | ||
| 43 | + | ||
| 44 | + // POST request with Content-Length header (should not be optimized) | ||
| 45 | + const postWithContentLength = 'POST / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n'; | ||
| 46 | + await makeRequest(postWithContentLength); | ||
| 47 | + | ||
| 48 | + // GET request with Content-Length header (should not be optimized) | ||
| 49 | + const getWithContentLength = 'GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n'; | ||
| 50 | + await makeRequest(getWithContentLength); | ||
| 51 | + | ||
| 52 | + // POST request with Transfer-Encoding header (should not be optimized) | ||
| 53 | + const postWithTransferEncoding = 'POST / HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\n\r\n'; | ||
| 54 | + await makeRequest(postWithTransferEncoding); | ||
| 55 | + | ||
| 56 | + // GET request with Transfer-Encoding header (should not be optimized) | ||
| 57 | + const getWithTransferEncoding = 'GET / HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\n\r\n'; | ||
| 58 | + await makeRequest(getWithTransferEncoding); | ||
| 59 | + | ||
| 60 | + server.close(); | ||
| 61 | + | ||
| 62 | + assert.strictEqual(reqs, 8, `Expected 8 requests but got ${reqs}`); | ||
| 63 | + assert.strictEqual(optimizedReqs, 4, `Expected 4 optimized requests but got ${optimizedReqs}`); | ||
| 64 | + })); | ||
| 65 | + | ||
| 66 | + function makeRequest(str) { | ||
| 67 | + return new Promise((resolve) => { | ||
| 68 | + const client = net.connect({ port: server.address().port }, common.mustCall(() => { | ||
| 69 | + client.on('data', () => {}); | ||
| 70 | + client.on('end', common.mustCall(() => { | ||
| 71 | + resolve(); | ||
| 72 | + })); | ||
| 73 | + client.write(str); | ||
| 74 | + client.end(); | ||
| 75 | + })); | ||
| 76 | + }); | ||
| 77 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments