Skip to content

Commit 5d1950d

Browse files
authored
feat: Add support for configurable maxHeaderSize in HTTP requests (#1511)
* Add support for configurable maxHeaderSize in HTTP requests This introduces the `maxHeaderSize` option for limiting HTTP header size in Node.js HTTP/1 requests. It includes implementation, tests for various scenarios, and updates to documentation. The feature is ignored for HTTP/2 where header sizing is not configurable. * refactor: remove dependency on 'http' module for maxHeaderSize * chore: update default maxHeaderSize to 128KB Increase the default `maxHeaderSize` from 16KB to 128KB to align with updated requirements and prevent potential header size limitations. Adjusted related test cases to reflect the new default configuration.
1 parent dd0cb73 commit 5d1950d

File tree

7 files changed

+206
-3
lines changed

7 files changed

+206
-3
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ runner.run(collection, {
9797
// Maximum allowed response size in bytes (only supported on Node, ignored in the browser)
9898
maxResponseSize: 1000000,
9999

100+
// Maximum allowed header size in bytes (only supported on Node HTTP/1, ignored in the browser and when using HTTP/2)
101+
maxHeaderSize: 1000000,
102+
100103
// HTTP Protocol version to use. Valid options are http1, http2, and auto (only supported on Node, ignored in the browser)
101104
protocolVersion: 'http1',
102105

@@ -125,7 +128,7 @@ runner.run(collection, {
125128
implicitTraceHeader: true,
126129

127130
// Add system headers to all requests which cannot be overridden or disabled
128-
systemHeaders: { 'User-Agent': 'PostmanRuntime' }
131+
systemHeaders: { 'User-Agent': 'PostmanRuntime' },
129132

130133
// Extend well known "root" CAs with the extra certificates in file. The file should consist of one or more trusted certificates in PEM format. (only supported on Node, ignored in the browser)
131134
extendedRootCA: 'path/to/extra/CA/certs.pem',

lib/requester/core.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ module.exports = {
407407
* @param defaultOpts.implicitTraceHeader
408408
* @param defaultOpts.removeRefererHeaderOnRedirect
409409
* @param defaultOpts.timings
410+
* @param defaultOpts.maxHeaderSize
410411
* @param protocolProfileBehavior
411412
* @returns {{}}
412413
*/
@@ -419,6 +420,7 @@ module.exports = {
419420
self = this,
420421
bodyParams,
421422
useWhatWGUrlParser = defaultOpts.useWhatWGUrlParser,
423+
maxHeaderSize = defaultOpts.maxHeaderSize,
422424
disableUrlEncoding = protocolProfileBehavior.disableUrlEncoding,
423425
disabledSystemHeaders = protocolProfileBehavior.disabledSystemHeaders || {},
424426
// the system headers provided in requester configuration
@@ -457,6 +459,7 @@ module.exports = {
457459
options.extraCA = defaultOpts.extendedRootCA;
458460
options.ignoreProxyEnvironmentVariables = defaultOpts.ignoreProxyEnvironmentVariables;
459461
options.agentIdleTimeout = defaultOpts.agentIdleTimeout;
462+
options.maxHeaderSize = maxHeaderSize;
460463

461464
// Disable encoding of URL in postman-request in order to use pre-encoded URL object returned from
462465
// toNodeUrl() function of postman-url-encoder

lib/requester/requester-pool.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ RequesterPool = function (options, callback) {
3737
systemHeaders: _.get(options, 'requester.systemHeaders', {}),
3838
removeRefererHeaderOnRedirect: _.get(options, 'requester.removeRefererHeaderOnRedirect'),
3939
ignoreProxyEnvironmentVariables: _.get(options, 'ignoreProxyEnvironmentVariables'),
40-
network: _.get(options, 'network', {})
40+
network: _.get(options, 'network', {}),
41+
maxHeaderSize: _.get(options, 'requester.maxHeaderSize', 131072) // 128KB
4142
});
4243

4344
// create a cookie jar if one is not provided

test/fixtures/servers/_servers.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,9 @@ function createHTTP2Server (opts) {
707707
options[option] = fs.readFileSync(options[option]);
708708
});
709709

710-
server = http2.createSecureServer(options, function (req, res) {
710+
server = http2.createSecureServer(options);
711+
712+
server.on('request', (req, res) => {
711713
server.emit(req.url, req, res);
712714
});
713715

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const server = require('./_servers'),
2+
httpServer = server.createHTTPServer();
3+
4+
5+
httpServer.on('request', function (req, res) {
6+
const headerSizeInBytes = parseInt(req.url.split('/').at(-1), 10);
7+
let headers = { header: 'a'.repeat(headerSizeInBytes - 6 - 2 - 2) };
8+
9+
res.writeHead(200, headers);
10+
res.end('Headers sent dynamically based on the URL.');
11+
});
12+
13+
14+
module.exports = httpServer;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const server = require('./_servers'),
2+
httpServer = server.createHTTP2Server();
3+
4+
5+
httpServer.on('request', (req, res) => {
6+
const headerSizeInBytes = parseInt(req.url.split('/').at(-1), 10);
7+
8+
let headers = { header: 'a'.repeat(headerSizeInBytes - 6 - 2 - 2) };
9+
10+
res.writeHead(200, headers);
11+
res.end('Headers sent dynamically based on the URL.');
12+
});
13+
14+
15+
module.exports = httpServer;
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
const expect = require('chai').expect,
2+
IS_NODE = typeof window === 'undefined';
3+
4+
(IS_NODE ? describe : describe.skip)('Requester Spec: maxHeaderSize', function () {
5+
let testrun,
6+
BASE_URL_HTTP1,
7+
BASE_URL_HTTP2;
8+
9+
before(function (done) {
10+
BASE_URL_HTTP1 = global.servers.dynamicHeadersHTTP1;
11+
BASE_URL_HTTP2 = global.servers.dynamicHeadersHTTP2;
12+
done();
13+
});
14+
15+
describe('HTTP/1: with maxHeaderSize: undefined (default)', function () {
16+
before(function (done) {
17+
this.run({
18+
collection: {
19+
item: [{
20+
request: {
21+
url: `${BASE_URL_HTTP1}/1024`, // 1KB
22+
method: 'GET'
23+
}
24+
}]
25+
}
26+
}, (err, results) => {
27+
testrun = results;
28+
done(err);
29+
});
30+
});
31+
32+
it('should complete the request successfully', function () {
33+
expect(testrun).to.be.ok;
34+
const response = testrun.response.getCall(0).args[2];
35+
36+
expect(response).to.have.property('code', 200); // HTTP OK
37+
});
38+
39+
it('should include the correct number of headers', function () {
40+
const response = testrun.response.getCall(0).args[2];
41+
42+
expect(response.headers.count()).to.be.eql(5);
43+
});
44+
});
45+
46+
describe('HTTP/1: with maxHeaderSize: undefined and exceeded', function () {
47+
before(function (done) {
48+
this.run({
49+
collection: {
50+
item: [{
51+
request: {
52+
url: `${BASE_URL_HTTP1}/132000`, // > 128KB default set
53+
method: 'GET'
54+
}
55+
}]
56+
}
57+
}, (err, results) => {
58+
testrun = results;
59+
done(err);
60+
});
61+
});
62+
63+
it('should not complete the request and throw an error', function () {
64+
const error = testrun.request.getCall(0).args[0];
65+
66+
expect(error).to.be.ok;
67+
expect(error.code).to.eql('HPE_HEADER_OVERFLOW'); // Node.js error
68+
});
69+
});
70+
71+
describe('HTTP/1: with maxHeaderSize: defined and exceeded', function () {
72+
const MAX_HEADER_SIZE = 512;
73+
74+
before(function (done) {
75+
this.run({
76+
collection: {
77+
item: [{
78+
request: {
79+
url: `${BASE_URL_HTTP1}/600`, // Send 50 headers
80+
method: 'GET'
81+
}
82+
}]
83+
},
84+
requester: {
85+
maxHeaderSize: MAX_HEADER_SIZE // Set a limit less than required to trigger failure
86+
}
87+
}, (err, results) => {
88+
testrun = results;
89+
done(err);
90+
});
91+
});
92+
93+
it('should not complete the request and throw an error', function () {
94+
const error = testrun.request.getCall(0).args[0];
95+
96+
expect(error).to.be.ok;
97+
expect(error.code).to.eql('HPE_HEADER_OVERFLOW'); // Node.js error
98+
});
99+
});
100+
101+
describe('HTTP/1: with maxHeaderSize: defined and respected', function () {
102+
const MAX_HEADER_SIZE = 8192;
103+
104+
before(function (done) {
105+
this.run({
106+
collection: {
107+
item: [{
108+
request: {
109+
url: `${BASE_URL_HTTP1}/500`, // Send 10 headers
110+
method: 'GET'
111+
}
112+
}]
113+
},
114+
requester: {
115+
maxHeaderSize: MAX_HEADER_SIZE // Set a limit more than required
116+
}
117+
}, (err, results) => {
118+
testrun = results;
119+
done(err);
120+
});
121+
});
122+
123+
it('should complete the request successfully', function () {
124+
expect(testrun).to.be.ok;
125+
const response = testrun.response.getCall(0).args[2];
126+
127+
expect(response).to.have.property('code', 200); // HTTP OK
128+
});
129+
130+
it('should include the correct number of headers', function () {
131+
const response = testrun.response.getCall(0).args[2];
132+
133+
expect(response.headers.count()).to.be.eql(5);
134+
});
135+
});
136+
137+
describe('HTTP/2: with maxHeaderSize: defined and exceeded (NO-OP)', function () {
138+
before(function (done) {
139+
this.run({
140+
collection: {
141+
item: [{
142+
request: {
143+
url: `${BASE_URL_HTTP2}/50`, // Send 50 headers
144+
method: 'GET'
145+
}
146+
}]
147+
},
148+
requester: {
149+
strictSSL: false,
150+
protocolVersion: 'auto',
151+
maxHeaderSize: 10 // Set a limit less than required to trigger failure
152+
}
153+
}, (err, results) => {
154+
testrun = results;
155+
done(err);
156+
});
157+
});
158+
159+
it('should include the correct number of headers', function () {
160+
const response = testrun.response.getCall(0).args[2];
161+
162+
expect(response.headers.count()).to.be.eql(3);
163+
});
164+
});
165+
});

0 commit comments

Comments
 (0)