Skip to content

Commit 43bf1f6

Browse files
pimterryaduh95
authored andcommitted
http2: add raw header array support to h2Session.request()
This also notably changes error handling for request(). Previously some invalid header values (but not all) would cause the session to be unnecessarily destroyed automatically, e.g. passing an unparseable header name to request(). This is no longer the case: header validation failures will throw an error, but will not destroy the session or emit 'error' events. PR-URL: #57917 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 9bcef68 commit 43bf1f6

10 files changed

+351
-128
lines changed

doc/api/http2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1078,7 +1078,7 @@ changes:
10781078
`weight` option is deprecated.
10791079
-->
10801080

1081-
* `headers` {HTTP/2 Headers Object}
1081+
* `headers` {HTTP/2 Headers Object} | {Array}
10821082

10831083
* `options` {Object}
10841084
* `endStream` {boolean} `true` if the `Http2Stream` _writable_ side should

lib/internal/http2/core.js

Lines changed: 50 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const {
99
ObjectDefineProperty,
1010
ObjectEntries,
1111
ObjectHasOwn,
12-
ObjectKeys,
1312
Promise,
1413
Proxy,
1514
ReflectApply,
@@ -47,10 +46,7 @@ const { Duplex } = require('stream');
4746
const tls = require('tls');
4847
const { setImmediate, setTimeout, clearTimeout } = require('timers');
4948

50-
const {
51-
kIncomingMessage,
52-
_checkIsHttpToken: checkIsHttpToken,
53-
} = require('_http_common');
49+
const { kIncomingMessage } = require('_http_common');
5450
const { kServerResponse, Server: HttpServer, httpServerPreClose, setupConnectionsTracking } = require('_http_server');
5551
const JSStreamSocket = require('internal/js_stream_socket');
5652

@@ -69,9 +65,6 @@ const {
6965
codes: {
7066
ERR_HTTP2_ALTSVC_INVALID_ORIGIN,
7167
ERR_HTTP2_ALTSVC_LENGTH,
72-
ERR_HTTP2_CONNECT_AUTHORITY,
73-
ERR_HTTP2_CONNECT_PATH,
74-
ERR_HTTP2_CONNECT_SCHEME,
7568
ERR_HTTP2_GOAWAY_SESSION,
7669
ERR_HTTP2_HEADERS_AFTER_RESPOND,
7770
ERR_HTTP2_HEADERS_SENT,
@@ -109,7 +102,6 @@ const {
109102
ERR_INVALID_ARG_TYPE,
110103
ERR_INVALID_ARG_VALUE,
111104
ERR_INVALID_CHAR,
112-
ERR_INVALID_HTTP_TOKEN,
113105
ERR_OUT_OF_RANGE,
114106
ERR_SOCKET_CLOSED,
115107
},
@@ -138,23 +130,26 @@ const {
138130
const {
139131
assertIsObject,
140132
assertIsArray,
141-
assertValidPseudoHeader,
142133
assertValidPseudoHeaderResponse,
143134
assertValidPseudoHeaderTrailer,
144135
assertWithinRange,
136+
buildNgHeaderString,
145137
getAuthority,
146138
getDefaultSettings,
147139
getSessionState,
148140
getSettings,
149141
getStreamState,
150142
isPayloadMeaningless,
143+
kAuthority,
151144
kSensitiveHeaders,
152145
kSocket,
153146
kRequest,
147+
kProtocol,
154148
kProxySocket,
155-
mapToHeaders,
156149
MAX_ADDITIONAL_SETTINGS,
157150
NghttpError,
151+
prepareRequestHeadersArray,
152+
prepareRequestHeadersObject,
158153
remoteCustomSettingsToBuffer,
159154
sessionName,
160155
toHeaderObject,
@@ -242,7 +237,6 @@ const NETServer = net.Server;
242237
const TLSServer = tls.Server;
243238

244239
const kAlpnProtocol = Symbol('alpnProtocol');
245-
const kAuthority = Symbol('authority');
246240
const kEncrypted = Symbol('encrypted');
247241
const kID = Symbol('id');
248242
const kInit = Symbol('init');
@@ -254,7 +248,6 @@ const kOwner = owner_symbol;
254248
const kOrigin = Symbol('origin');
255249
const kPendingRequestCalls = Symbol('kPendingRequestCalls');
256250
const kProceed = Symbol('proceed');
257-
const kProtocol = Symbol('protocol');
258251
const kRemoteSettings = Symbol('remote-settings');
259252
const kRequestAsyncResource = Symbol('requestAsyncResource');
260253
const kSentHeaders = Symbol('sent-headers');
@@ -297,7 +290,6 @@ const {
297290
HTTP2_HEADER_DATE,
298291
HTTP2_HEADER_METHOD,
299292
HTTP2_HEADER_PATH,
300-
HTTP2_HEADER_PROTOCOL,
301293
HTTP2_HEADER_SCHEME,
302294
HTTP2_HEADER_STATUS,
303295
HTTP2_HEADER_CONTENT_LENGTH,
@@ -312,7 +304,6 @@ const {
312304

313305
HTTP2_METHOD_GET,
314306
HTTP2_METHOD_HEAD,
315-
HTTP2_METHOD_CONNECT,
316307

317308
HTTP_STATUS_CONTINUE,
318309
HTTP_STATUS_RESET_CONTENT,
@@ -1808,7 +1799,7 @@ class ClientHttp2Session extends Http2Session {
18081799

18091800
// Submits a new HTTP2 request to the connected peer. Returns the
18101801
// associated Http2Stream instance.
1811-
request(headers, options) {
1802+
request(headersParam, options) {
18121803
debugSessionObj(this, 'initiating request');
18131804

18141805
if (this.destroyed)
@@ -1819,62 +1810,61 @@ class ClientHttp2Session extends Http2Session {
18191810

18201811
this[kUpdateTimer]();
18211812

1822-
if (headers !== null && headers !== undefined) {
1823-
const keys = ObjectKeys(headers);
1824-
for (let i = 0; i < keys.length; i++) {
1825-
const header = keys[i];
1826-
if (header[0] === ':') {
1827-
assertValidPseudoHeader(header);
1828-
} else if (header && !checkIsHttpToken(header))
1829-
this.destroy(new ERR_INVALID_HTTP_TOKEN('Header name', header));
1830-
}
1813+
let headersList;
1814+
let headersObject;
1815+
let scheme;
1816+
let authority;
1817+
let method;
1818+
1819+
if (ArrayIsArray(headersParam)) {
1820+
({
1821+
headersList,
1822+
scheme,
1823+
authority,
1824+
method,
1825+
} = prepareRequestHeadersArray(headersParam, this));
1826+
} else if (!!headersParam && typeof headersParam === 'object') {
1827+
({
1828+
headersObject,
1829+
headersList,
1830+
scheme,
1831+
authority,
1832+
method,
1833+
} = prepareRequestHeadersObject(headersParam, this));
1834+
} else if (headersParam === undefined) {
1835+
({
1836+
headersObject,
1837+
headersList,
1838+
scheme,
1839+
authority,
1840+
method,
1841+
} = prepareRequestHeadersObject({}, this));
1842+
} else {
1843+
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(
1844+
'headers',
1845+
['Object', 'Array'],
1846+
headersParam,
1847+
);
18311848
}
18321849

1833-
assertIsObject(headers, 'headers');
18341850
assertIsObject(options, 'options');
1835-
1836-
headers = ObjectAssign({ __proto__: null }, headers);
18371851
options = { ...options };
18381852

1839-
if (headers[HTTP2_HEADER_METHOD] === undefined)
1840-
headers[HTTP2_HEADER_METHOD] = HTTP2_METHOD_GET;
1841-
1842-
const connect = headers[HTTP2_HEADER_METHOD] === HTTP2_METHOD_CONNECT;
1843-
1844-
if (!connect || headers[HTTP2_HEADER_PROTOCOL] !== undefined) {
1845-
if (getAuthority(headers) === undefined)
1846-
headers[HTTP2_HEADER_AUTHORITY] = this[kAuthority];
1847-
if (headers[HTTP2_HEADER_SCHEME] === undefined)
1848-
headers[HTTP2_HEADER_SCHEME] = this[kProtocol].slice(0, -1);
1849-
if (headers[HTTP2_HEADER_PATH] === undefined)
1850-
headers[HTTP2_HEADER_PATH] = '/';
1851-
} else {
1852-
if (headers[HTTP2_HEADER_AUTHORITY] === undefined)
1853-
throw new ERR_HTTP2_CONNECT_AUTHORITY();
1854-
if (headers[HTTP2_HEADER_SCHEME] !== undefined)
1855-
throw new ERR_HTTP2_CONNECT_SCHEME();
1856-
if (headers[HTTP2_HEADER_PATH] !== undefined)
1857-
throw new ERR_HTTP2_CONNECT_PATH();
1858-
}
1859-
18601853
setAndValidatePriorityOptions(options);
18611854

18621855
if (options.endStream === undefined) {
18631856
// For some methods, we know that a payload is meaningless, so end the
18641857
// stream by default if the user has not specifically indicated a
18651858
// preference.
1866-
options.endStream = isPayloadMeaningless(headers[HTTP2_HEADER_METHOD]);
1859+
options.endStream = isPayloadMeaningless(method);
18671860
} else {
18681861
validateBoolean(options.endStream, 'options.endStream');
18691862
}
18701863

1871-
const headersList = mapToHeaders(headers);
1872-
18731864
// eslint-disable-next-line no-use-before-define
18741865
const stream = new ClientHttp2Stream(this, undefined, undefined, {});
1875-
stream[kSentHeaders] = headers;
1876-
stream[kOrigin] = `${headers[HTTP2_HEADER_SCHEME]}://` +
1877-
`${getAuthority(headers)}`;
1866+
stream[kSentHeaders] = headersObject; // N.b. Only set for object headers, not raw headers
1867+
stream[kOrigin] = `${scheme}://${authority}`;
18781868
const reqAsync = new AsyncResource('PendingRequest');
18791869
stream[kRequestAsyncResource] = reqAsync;
18801870

@@ -2350,7 +2340,7 @@ class Http2Stream extends Duplex {
23502340

23512341
this[kUpdateTimer]();
23522342

2353-
const headersList = mapToHeaders(headers, assertValidPseudoHeaderTrailer);
2343+
const headersList = buildNgHeaderString(headers, assertValidPseudoHeaderTrailer);
23542344
this[kSentTrailers] = headers;
23552345

23562346
// Send the trailers in setImmediate so we don't do it on nghttp2 stack.
@@ -2598,7 +2588,7 @@ function processRespondWithFD(self, fd, headers, offset = 0, length = -1,
25982588

25992589
let headersList;
26002590
try {
2601-
headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
2591+
headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse);
26022592
} catch (err) {
26032593
self.destroy(err);
26042594
return;
@@ -2822,7 +2812,7 @@ class ServerHttp2Stream extends Http2Stream {
28222812
if (headers[HTTP2_HEADER_METHOD] === HTTP2_METHOD_HEAD)
28232813
headRequest = options.endStream = true;
28242814

2825-
const headersList = mapToHeaders(headers);
2815+
const headersList = buildNgHeaderString(headers);
28262816

28272817
const streamOptions = options.endStream ? STREAM_OPTION_EMPTY_PAYLOAD : 0;
28282818

@@ -2902,7 +2892,7 @@ class ServerHttp2Stream extends Http2Stream {
29022892
}
29032893

29042894
headers = processHeaders(headers, options);
2905-
const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
2895+
const headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse);
29062896
this[kSentHeaders] = headers;
29072897

29082898
state.flags |= STREAM_FLAGS_HEADERS_SENT;
@@ -3079,7 +3069,7 @@ class ServerHttp2Stream extends Http2Stream {
30793069

30803070
this[kUpdateTimer]();
30813071

3082-
const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
3072+
const headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse);
30833073
if (!this[kInfoHeaders])
30843074
this[kInfoHeaders] = [headers];
30853075
else

0 commit comments

Comments
 (0)