http2: add compat trailers, adjust multi-headers
Adds Http2ServerRequest trailers & rawTrailers functionality. Also fixes behaviour of multi-headers to conform with the spec (all values but set-cookie and cookie should be comma delimited, cookie should be semi-colon delimited and only set-cookie should be an array). Adds setter for statusMessage that warns, for backwards compatibility. End readable side of the stream on trailers or bodyless requests Refs: https://github.com/expressjs/express/pull/3390#discussion_r136718729 PR-URL: https://github.com/nodejs/node/pull/15193 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
parent
447715543b
commit
641646463d
@ -13,7 +13,9 @@ const kStream = Symbol('stream');
|
|||||||
const kRequest = Symbol('request');
|
const kRequest = Symbol('request');
|
||||||
const kResponse = Symbol('response');
|
const kResponse = Symbol('response');
|
||||||
const kHeaders = Symbol('headers');
|
const kHeaders = Symbol('headers');
|
||||||
|
const kRawHeaders = Symbol('rawHeaders');
|
||||||
const kTrailers = Symbol('trailers');
|
const kTrailers = Symbol('trailers');
|
||||||
|
const kRawTrailers = Symbol('rawTrailers');
|
||||||
|
|
||||||
let statusMessageWarned = false;
|
let statusMessageWarned = false;
|
||||||
|
|
||||||
@ -45,12 +47,28 @@ function isPseudoHeader(name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function statusMessageWarn() {
|
||||||
|
if (statusMessageWarned === false) {
|
||||||
|
process.emitWarning(
|
||||||
|
'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)',
|
||||||
|
'UnsupportedWarning'
|
||||||
|
);
|
||||||
|
statusMessageWarned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onStreamData(chunk) {
|
function onStreamData(chunk) {
|
||||||
const request = this[kRequest];
|
const request = this[kRequest];
|
||||||
if (!request.push(chunk))
|
if (!request.push(chunk))
|
||||||
this.pause();
|
this.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onStreamTrailers(trailers, flags, rawTrailers) {
|
||||||
|
const request = this[kRequest];
|
||||||
|
Object.assign(request[kTrailers], trailers);
|
||||||
|
request[kRawTrailers].push(...rawTrailers);
|
||||||
|
}
|
||||||
|
|
||||||
function onStreamEnd() {
|
function onStreamEnd() {
|
||||||
// Cause the request stream to end as well.
|
// Cause the request stream to end as well.
|
||||||
const request = this[kRequest];
|
const request = this[kRequest];
|
||||||
@ -106,7 +124,7 @@ function onAborted(hadError, code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Http2ServerRequest extends Readable {
|
class Http2ServerRequest extends Readable {
|
||||||
constructor(stream, headers, options) {
|
constructor(stream, headers, options, rawHeaders) {
|
||||||
super(options);
|
super(options);
|
||||||
this[kState] = {
|
this[kState] = {
|
||||||
statusCode: null,
|
statusCode: null,
|
||||||
@ -114,12 +132,16 @@ class Http2ServerRequest extends Readable {
|
|||||||
closedCode: constants.NGHTTP2_NO_ERROR
|
closedCode: constants.NGHTTP2_NO_ERROR
|
||||||
};
|
};
|
||||||
this[kHeaders] = headers;
|
this[kHeaders] = headers;
|
||||||
|
this[kRawHeaders] = rawHeaders;
|
||||||
|
this[kTrailers] = {};
|
||||||
|
this[kRawTrailers] = [];
|
||||||
this[kStream] = stream;
|
this[kStream] = stream;
|
||||||
stream[kRequest] = this;
|
stream[kRequest] = this;
|
||||||
|
|
||||||
// Pause the stream..
|
// Pause the stream..
|
||||||
stream.pause();
|
stream.pause();
|
||||||
stream.on('data', onStreamData);
|
stream.on('data', onStreamData);
|
||||||
|
stream.on('trailers', onStreamTrailers);
|
||||||
stream.on('end', onStreamEnd);
|
stream.on('end', onStreamEnd);
|
||||||
stream.on('error', onStreamError);
|
stream.on('error', onStreamError);
|
||||||
stream.on('close', onStreamClosedRequest);
|
stream.on('close', onStreamClosedRequest);
|
||||||
@ -155,18 +177,17 @@ class Http2ServerRequest extends Readable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get rawHeaders() {
|
get rawHeaders() {
|
||||||
const headers = this[kHeaders];
|
return this[kRawHeaders];
|
||||||
if (headers === undefined)
|
|
||||||
return [];
|
|
||||||
const tuples = Object.entries(headers);
|
|
||||||
const flattened = Array.prototype.concat.apply([], tuples);
|
|
||||||
return flattened.map(String);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get trailers() {
|
get trailers() {
|
||||||
return this[kTrailers];
|
return this[kTrailers];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get rawTrailers() {
|
||||||
|
return this[kRawTrailers];
|
||||||
|
}
|
||||||
|
|
||||||
get httpVersionMajor() {
|
get httpVersionMajor() {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
@ -382,30 +403,25 @@ class Http2ServerResponse extends Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get statusMessage() {
|
get statusMessage() {
|
||||||
if (statusMessageWarned === false) {
|
statusMessageWarn();
|
||||||
process.emitWarning(
|
|
||||||
'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)',
|
|
||||||
'UnsupportedWarning'
|
|
||||||
);
|
|
||||||
statusMessageWarned = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set statusMessage(msg) {
|
||||||
|
statusMessageWarn();
|
||||||
|
}
|
||||||
|
|
||||||
flushHeaders() {
|
flushHeaders() {
|
||||||
if (this[kStream].headersSent === false)
|
if (this[kStream].headersSent === false)
|
||||||
this[kBeginSend]();
|
this[kBeginSend]();
|
||||||
}
|
}
|
||||||
|
|
||||||
writeHead(statusCode, statusMessage, headers) {
|
writeHead(statusCode, statusMessage, headers) {
|
||||||
if (typeof statusMessage === 'string' && statusMessageWarned === false) {
|
if (typeof statusMessage === 'string') {
|
||||||
process.emitWarning(
|
statusMessageWarn();
|
||||||
'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)',
|
|
||||||
'UnsupportedWarning'
|
|
||||||
);
|
|
||||||
statusMessageWarned = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (headers === undefined && typeof statusMessage === 'object') {
|
if (headers === undefined && typeof statusMessage === 'object') {
|
||||||
headers = statusMessage;
|
headers = statusMessage;
|
||||||
}
|
}
|
||||||
@ -542,9 +558,10 @@ class Http2ServerResponse extends Stream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onServerStream(stream, headers, flags) {
|
function onServerStream(stream, headers, flags, rawHeaders) {
|
||||||
const server = this;
|
const server = this;
|
||||||
const request = new Http2ServerRequest(stream, headers);
|
const request = new Http2ServerRequest(stream, headers, undefined,
|
||||||
|
rawHeaders);
|
||||||
const response = new Http2ServerResponse(stream);
|
const response = new Http2ServerResponse(stream);
|
||||||
|
|
||||||
// Check for the CONNECT method
|
// Check for the CONNECT method
|
||||||
|
@ -185,7 +185,7 @@ function onSessionHeaders(id, cat, flags, headers) {
|
|||||||
'report this as a bug in Node.js');
|
'report this as a bug in Node.js');
|
||||||
}
|
}
|
||||||
streams.set(id, stream);
|
streams.set(id, stream);
|
||||||
process.nextTick(emit.bind(owner, 'stream', stream, obj, flags));
|
process.nextTick(emit.bind(owner, 'stream', stream, obj, flags, headers));
|
||||||
} else {
|
} else {
|
||||||
let event;
|
let event;
|
||||||
let status;
|
let status;
|
||||||
@ -218,7 +218,10 @@ function onSessionHeaders(id, cat, flags, headers) {
|
|||||||
'report this as a bug in Node.js');
|
'report this as a bug in Node.js');
|
||||||
}
|
}
|
||||||
debug(`[${sessionName(owner[kType])}] emitting stream '${event}' event`);
|
debug(`[${sessionName(owner[kType])}] emitting stream '${event}' event`);
|
||||||
process.nextTick(emit.bind(stream, event, obj, flags));
|
process.nextTick(emit.bind(stream, event, obj, flags, headers));
|
||||||
|
}
|
||||||
|
if (endOfStream) {
|
||||||
|
stream.push(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2271,9 +2274,9 @@ function socketOnTimeout() {
|
|||||||
|
|
||||||
// Handles the on('stream') event for a session and forwards
|
// Handles the on('stream') event for a session and forwards
|
||||||
// it on to the server object.
|
// it on to the server object.
|
||||||
function sessionOnStream(stream, headers, flags) {
|
function sessionOnStream(stream, headers, flags, rawHeaders) {
|
||||||
debug(`[${sessionName(this[kType])}] emit server stream event`);
|
debug(`[${sessionName(this[kType])}] emit server stream event`);
|
||||||
this[kServer].emit('stream', stream, headers, flags);
|
this[kServer].emit('stream', stream, headers, flags, rawHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sessionOnPriority(stream, parent, weight, exclusive) {
|
function sessionOnPriority(stream, parent, weight, exclusive) {
|
||||||
|
@ -35,6 +35,7 @@ const {
|
|||||||
HTTP2_HEADER_RANGE,
|
HTTP2_HEADER_RANGE,
|
||||||
HTTP2_HEADER_REFERER,
|
HTTP2_HEADER_REFERER,
|
||||||
HTTP2_HEADER_RETRY_AFTER,
|
HTTP2_HEADER_RETRY_AFTER,
|
||||||
|
HTTP2_HEADER_SET_COOKIE,
|
||||||
HTTP2_HEADER_USER_AGENT,
|
HTTP2_HEADER_USER_AGENT,
|
||||||
|
|
||||||
HTTP2_HEADER_CONNECTION,
|
HTTP2_HEADER_CONNECTION,
|
||||||
@ -474,18 +475,36 @@ function toHeaderObject(headers) {
|
|||||||
if (existing === undefined) {
|
if (existing === undefined) {
|
||||||
obj[name] = value;
|
obj[name] = value;
|
||||||
} else if (!kSingleValueHeaders.has(name)) {
|
} else if (!kSingleValueHeaders.has(name)) {
|
||||||
if (name === HTTP2_HEADER_COOKIE) {
|
switch (name) {
|
||||||
// https://tools.ietf.org/html/rfc7540#section-8.1.2.5
|
case HTTP2_HEADER_COOKIE:
|
||||||
// "...If there are multiple Cookie header fields after decompression,
|
// https://tools.ietf.org/html/rfc7540#section-8.1.2.5
|
||||||
// these MUST be concatenated into a single octet string using the
|
// "...If there are multiple Cookie header fields after decompression,
|
||||||
// two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ") before
|
// these MUST be concatenated into a single octet string using the
|
||||||
// being passed into a non-HTTP/2 context."
|
// two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ") before
|
||||||
obj[name] = `${existing}; ${value}`;
|
// being passed into a non-HTTP/2 context."
|
||||||
} else {
|
obj[name] = `${existing}; ${value}`;
|
||||||
if (Array.isArray(existing))
|
break;
|
||||||
existing.push(value);
|
case HTTP2_HEADER_SET_COOKIE:
|
||||||
else
|
// https://tools.ietf.org/html/rfc7230#section-3.2.2
|
||||||
obj[name] = [existing, value];
|
// "Note: In practice, the "Set-Cookie" header field ([RFC6265]) often
|
||||||
|
// appears multiple times in a response message and does not use the
|
||||||
|
// list syntax, violating the above requirements on multiple header
|
||||||
|
// fields with the same name. Since it cannot be combined into a
|
||||||
|
// single field-value, recipients ought to handle "Set-Cookie" as a
|
||||||
|
// special case while processing header fields."
|
||||||
|
if (Array.isArray(existing))
|
||||||
|
existing.push(value);
|
||||||
|
else
|
||||||
|
obj[name] = [existing, value];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// https://tools.ietf.org/html/rfc7230#section-3.2.2
|
||||||
|
// "A recipient MAY combine multiple header fields with the same field
|
||||||
|
// name into one "field-name: field-value" pair, without changing the
|
||||||
|
// semantics of the message, by appending each subsequent field value
|
||||||
|
// to the combined field value in order, separated by a comma."
|
||||||
|
obj[name] = `${existing}, ${value}`;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
40
test/parallel/test-http2-compat-serverrequest-end.js
Normal file
40
test/parallel/test-http2-compat-serverrequest-end.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Flags: --expose-http2
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
const h2 = require('http2');
|
||||||
|
|
||||||
|
// Http2ServerRequest should always end readable stream
|
||||||
|
// even on GET requests with no body
|
||||||
|
|
||||||
|
const server = h2.createServer();
|
||||||
|
server.listen(0, common.mustCall(function() {
|
||||||
|
const port = server.address().port;
|
||||||
|
server.once('request', common.mustCall(function(request, response) {
|
||||||
|
request.on('data', () => {});
|
||||||
|
request.on('end', common.mustCall(() => {
|
||||||
|
response.on('finish', common.mustCall(function() {
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
response.end();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
const url = `http://localhost:${port}`;
|
||||||
|
const client = h2.connect(url, common.mustCall(function() {
|
||||||
|
const headers = {
|
||||||
|
':path': '/foobar',
|
||||||
|
':method': 'GET',
|
||||||
|
':scheme': 'http',
|
||||||
|
':authority': `localhost:${port}`
|
||||||
|
};
|
||||||
|
const request = client.request(headers);
|
||||||
|
request.resume();
|
||||||
|
request.on('end', common.mustCall(function() {
|
||||||
|
client.destroy();
|
||||||
|
}));
|
||||||
|
request.end();
|
||||||
|
}));
|
||||||
|
}));
|
71
test/parallel/test-http2-compat-serverrequest-trailers.js
Normal file
71
test/parallel/test-http2-compat-serverrequest-trailers.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Flags: --expose-http2
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
const assert = require('assert');
|
||||||
|
const h2 = require('http2');
|
||||||
|
|
||||||
|
// Http2ServerRequest should have getter for trailers & rawTrailers
|
||||||
|
|
||||||
|
const expectedTrailers = {
|
||||||
|
'x-foo': 'xOxOxOx, OxOxOxO, xOxOxOx, OxOxOxO',
|
||||||
|
'x-foo-test': 'test, test'
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = h2.createServer();
|
||||||
|
server.listen(0, common.mustCall(function() {
|
||||||
|
const port = server.address().port;
|
||||||
|
server.once('request', common.mustCall(function(request, response) {
|
||||||
|
let data = '';
|
||||||
|
request.setEncoding('utf8');
|
||||||
|
request.on('data', common.mustCall((chunk) => data += chunk));
|
||||||
|
request.on('end', common.mustCall(() => {
|
||||||
|
const trailers = request.trailers;
|
||||||
|
for (const [name, value] of Object.entries(expectedTrailers)) {
|
||||||
|
assert.strictEqual(trailers[name], value);
|
||||||
|
}
|
||||||
|
assert.deepStrictEqual([
|
||||||
|
'x-foo',
|
||||||
|
'xOxOxOx',
|
||||||
|
'x-foo',
|
||||||
|
'OxOxOxO',
|
||||||
|
'x-foo',
|
||||||
|
'xOxOxOx',
|
||||||
|
'x-foo',
|
||||||
|
'OxOxOxO',
|
||||||
|
'x-foo-test',
|
||||||
|
'test, test'
|
||||||
|
], request.rawTrailers);
|
||||||
|
assert.strictEqual(data, 'test\ntest');
|
||||||
|
response.end();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
const url = `http://localhost:${port}`;
|
||||||
|
const client = h2.connect(url, common.mustCall(function() {
|
||||||
|
const headers = {
|
||||||
|
':path': '/foobar',
|
||||||
|
':method': 'POST',
|
||||||
|
':scheme': 'http',
|
||||||
|
':authority': `localhost:${port}`
|
||||||
|
};
|
||||||
|
const request = client.request(headers, {
|
||||||
|
getTrailers(trailers) {
|
||||||
|
trailers['x-fOo'] = 'xOxOxOx';
|
||||||
|
trailers['x-foO'] = 'OxOxOxO';
|
||||||
|
trailers['X-fOo'] = 'xOxOxOx';
|
||||||
|
trailers['X-foO'] = 'OxOxOxO';
|
||||||
|
trailers['x-foo-test'] = 'test, test';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request.resume();
|
||||||
|
request.on('end', common.mustCall(function() {
|
||||||
|
server.close();
|
||||||
|
client.destroy();
|
||||||
|
}));
|
||||||
|
request.write('test\n');
|
||||||
|
request.end('test');
|
||||||
|
}));
|
||||||
|
}));
|
@ -0,0 +1,51 @@
|
|||||||
|
// Flags: --expose-http2
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
const assert = require('assert');
|
||||||
|
const h2 = require('http2');
|
||||||
|
|
||||||
|
// Http2ServerResponse.statusMessage should warn
|
||||||
|
|
||||||
|
const unsupportedWarned = common.mustCall(1);
|
||||||
|
process.on('warning', ({ name, message }) => {
|
||||||
|
const expectedMessage =
|
||||||
|
'Status message is not supported by HTTP/2 (RFC7540 8.1.2.4)';
|
||||||
|
if (name === 'UnsupportedWarning' && message === expectedMessage)
|
||||||
|
unsupportedWarned();
|
||||||
|
});
|
||||||
|
|
||||||
|
const server = h2.createServer();
|
||||||
|
server.listen(0, common.mustCall(function() {
|
||||||
|
const port = server.address().port;
|
||||||
|
server.once('request', common.mustCall(function(request, response) {
|
||||||
|
response.on('finish', common.mustCall(function() {
|
||||||
|
response.statusMessage = 'test';
|
||||||
|
response.statusMessage = 'test'; // only warn once
|
||||||
|
assert.strictEqual(response.statusMessage, ''); // no change
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
response.end();
|
||||||
|
}));
|
||||||
|
|
||||||
|
const url = `http://localhost:${port}`;
|
||||||
|
const client = h2.connect(url, common.mustCall(function() {
|
||||||
|
const headers = {
|
||||||
|
':path': '/',
|
||||||
|
':method': 'GET',
|
||||||
|
':scheme': 'http',
|
||||||
|
':authority': `localhost:${port}`
|
||||||
|
};
|
||||||
|
const request = client.request(headers);
|
||||||
|
request.on('response', common.mustCall(function(headers) {
|
||||||
|
assert.strictEqual(headers[':status'], 200);
|
||||||
|
}, 1));
|
||||||
|
request.on('end', common.mustCall(function() {
|
||||||
|
client.destroy();
|
||||||
|
}));
|
||||||
|
request.end();
|
||||||
|
request.resume();
|
||||||
|
}));
|
||||||
|
}));
|
@ -23,6 +23,7 @@ server.listen(0, common.mustCall(function() {
|
|||||||
server.once('request', common.mustCall(function(request, response) {
|
server.once('request', common.mustCall(function(request, response) {
|
||||||
response.on('finish', common.mustCall(function() {
|
response.on('finish', common.mustCall(function() {
|
||||||
assert.strictEqual(response.statusMessage, '');
|
assert.strictEqual(response.statusMessage, '');
|
||||||
|
assert.strictEqual(response.statusMessage, ''); // only warn once
|
||||||
server.close();
|
server.close();
|
||||||
}));
|
}));
|
||||||
response.end();
|
response.end();
|
||||||
|
@ -19,11 +19,8 @@ server.on('stream', common.mustCall(onStream));
|
|||||||
|
|
||||||
function onStream(stream, headers, flags) {
|
function onStream(stream, headers, flags) {
|
||||||
|
|
||||||
assert(Array.isArray(headers.abc));
|
assert.strictEqual(typeof headers.abc, 'string');
|
||||||
assert.strictEqual(headers.abc.length, 3);
|
assert.strictEqual(headers.abc, '1, 2, 3');
|
||||||
assert.strictEqual(headers.abc[0], '1');
|
|
||||||
assert.strictEqual(headers.abc[1], '2');
|
|
||||||
assert.strictEqual(headers.abc[2], '3');
|
|
||||||
assert.strictEqual(typeof headers.cookie, 'string');
|
assert.strictEqual(typeof headers.cookie, 'string');
|
||||||
assert.strictEqual(headers.cookie, 'a=b; c=d; e=f');
|
assert.strictEqual(headers.cookie, 'a=b; c=d; e=f');
|
||||||
|
|
||||||
|
50
test/parallel/test-http2-multiheaders-raw.js
Normal file
50
test/parallel/test-http2-multiheaders-raw.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Flags: --expose-http2
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
const assert = require('assert');
|
||||||
|
const http2 = require('http2');
|
||||||
|
|
||||||
|
const server = http2.createServer();
|
||||||
|
|
||||||
|
const src = Object.create(null);
|
||||||
|
src['www-authenticate'] = 'foo';
|
||||||
|
src['WWW-Authenticate'] = 'bar';
|
||||||
|
src['WWW-AUTHENTICATE'] = 'baz';
|
||||||
|
src['test'] = 'foo, bar, baz';
|
||||||
|
|
||||||
|
server.on('stream', common.mustCall((stream, headers, flags, rawHeaders) => {
|
||||||
|
const expected = [
|
||||||
|
':path',
|
||||||
|
'/',
|
||||||
|
':scheme',
|
||||||
|
'http',
|
||||||
|
':authority',
|
||||||
|
`localhost:${server.address().port}`,
|
||||||
|
':method',
|
||||||
|
'GET',
|
||||||
|
'www-authenticate',
|
||||||
|
'foo',
|
||||||
|
'www-authenticate',
|
||||||
|
'bar',
|
||||||
|
'www-authenticate',
|
||||||
|
'baz',
|
||||||
|
'test',
|
||||||
|
'foo, bar, baz'
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.deepStrictEqual(expected, rawHeaders);
|
||||||
|
stream.respond(src);
|
||||||
|
stream.end();
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.listen(0, common.mustCall(() => {
|
||||||
|
const client = http2.connect(`http://localhost:${server.address().port}`);
|
||||||
|
const req = client.request(src);
|
||||||
|
req.on('streamClosed', common.mustCall(() => {
|
||||||
|
server.close();
|
||||||
|
client.destroy();
|
||||||
|
}));
|
||||||
|
}));
|
@ -31,15 +31,15 @@ src['__Proto__'] = 'baz';
|
|||||||
|
|
||||||
function checkHeaders(headers) {
|
function checkHeaders(headers) {
|
||||||
assert.deepStrictEqual(headers['accept'],
|
assert.deepStrictEqual(headers['accept'],
|
||||||
[ 'abc', 'def', 'ghijklmnop' ]);
|
'abc, def, ghijklmnop');
|
||||||
assert.deepStrictEqual(headers['www-authenticate'],
|
assert.deepStrictEqual(headers['www-authenticate'],
|
||||||
[ 'foo', 'bar', 'baz' ]);
|
'foo, bar, baz');
|
||||||
assert.deepStrictEqual(headers['proxy-authenticate'],
|
assert.deepStrictEqual(headers['proxy-authenticate'],
|
||||||
[ 'foo', 'bar', 'baz' ]);
|
'foo, bar, baz');
|
||||||
assert.deepStrictEqual(headers['x-foo'], [ 'foo', 'bar', 'baz' ]);
|
assert.deepStrictEqual(headers['x-foo'], 'foo, bar, baz');
|
||||||
assert.deepStrictEqual(headers['constructor'], [ 'foo', 'bar', 'baz' ]);
|
assert.deepStrictEqual(headers['constructor'], 'foo, bar, baz');
|
||||||
// eslint-disable-next-line no-proto
|
// eslint-disable-next-line no-proto
|
||||||
assert.deepStrictEqual(headers['__proto__'], [ 'foo', 'bar', 'baz' ]);
|
assert.deepStrictEqual(headers['__proto__'], 'foo, bar, baz');
|
||||||
}
|
}
|
||||||
|
|
||||||
server.on('stream', common.mustCall((stream, headers) => {
|
server.on('stream', common.mustCall((stream, headers) => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user