Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http2: remember sent headers #18045

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions doc/api/http2.md
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,34 @@ destroyed after either receiving an `RST_STREAM` frame from the connected peer,
calling `http2stream.close()`, or `http2stream.destroy()`. Will be
`undefined` if the `Http2Stream` has not been closed.

#### http2stream.sentHeaders
<!-- YAML
added: REPLACEME
-->

* Value: {[Headers Object][]}

An object containing the outbound headers sent for this `Http2Stream`.

#### http2stream.sentInfoHeaders
<!-- YAML
added: REPLACEME
-->

* Value: {[Headers Object][]\[\]}

An array of objects containing the outbound informational (additional) headers
sent for this `Http2Stream`.

#### http2stream.sentTrailers
<!-- YAML
added: REPLACEME
-->

* Value: {[Headers Object][]}

An object containing the outbound trailers sent for this this `HttpStream`.

#### http2stream.session
<!-- YAML
added: v8.4.0
Expand Down
24 changes: 24 additions & 0 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const kEncrypted = Symbol('encrypted');
const kHandle = Symbol('handle');
const kID = Symbol('id');
const kInit = Symbol('init');
const kInfoHeaders = Symbol('sent-info-headers');
const kMaybeDestroy = Symbol('maybe-destroy');
const kLocalSettings = Symbol('local-settings');
const kOptions = Symbol('options');
Expand All @@ -84,6 +85,8 @@ const kProceed = Symbol('proceed');
const kProtocol = Symbol('protocol');
const kProxySocket = Symbol('proxy-socket');
const kRemoteSettings = Symbol('remote-settings');
const kSentHeaders = Symbol('sent-headers');
const kSentTrailers = Symbol('sent-trailers');
const kServer = Symbol('server');
const kSession = Symbol('session');
const kState = Symbol('state');
Expand Down Expand Up @@ -258,6 +261,7 @@ function onStreamTrailers() {
stream.destroy(headersList);
return [];
}
stream[kSentTrailers] = trailers;
return headersList;
}

Expand Down Expand Up @@ -1348,6 +1352,7 @@ class ClientHttp2Session extends Http2Session {
throw headersList;

const stream = new ClientHttp2Stream(this, undefined, undefined, {});
stream[kSentHeaders] = headers;

// Close the writable side of the stream if options.endStream is set.
if (options.endStream)
Expand Down Expand Up @@ -1514,6 +1519,18 @@ class Http2Stream extends Duplex {
return `Http2Stream ${util.format(obj)}`;
}

get sentHeaders() {
return this[kSentHeaders];
}

get sentTrailers() {
return this[kSentTrailers];
}

get sentInfoHeaders() {
return this[kInfoHeaders];
}

get pending() {
return this[kID] === undefined;
}
Expand Down Expand Up @@ -1855,6 +1872,7 @@ function processRespondWithFD(self, fd, headers, offset = 0, length = -1,
state.flags |= STREAM_FLAGS_HEADERS_SENT;

const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
self[kSentHeaders] = headers;
if (!Array.isArray(headersList)) {
self.destroy(headersList);
return;
Expand Down Expand Up @@ -2085,6 +2103,7 @@ class ServerHttp2Stream extends Http2Stream {

const id = ret.id();
const stream = new ServerHttp2Stream(session, ret, id, options, headers);
stream[kSentHeaders] = headers;

if (options.endStream)
stream.end();
Expand Down Expand Up @@ -2144,6 +2163,7 @@ class ServerHttp2Stream extends Http2Stream {
const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
if (!Array.isArray(headersList))
throw headersList;
this[kSentHeaders] = headers;

state.flags |= STREAM_FLAGS_HEADERS_SENT;

Expand Down Expand Up @@ -2329,6 +2349,10 @@ class ServerHttp2Stream extends Http2Stream {
const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
if (!Array.isArray(headersList))
throw headersList;
if (!this[kInfoHeaders])
this[kInfoHeaders] = [headers];
else
this[kInfoHeaders].push(headers);

const ret = this[kHandle].info(headersList);
if (ret < 0)
Expand Down
47 changes: 47 additions & 0 deletions test/parallel/test-http2-sent-headers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const h2 = require('http2');

const server = h2.createServer();

server.on('stream', common.mustCall((stream) => {
stream.additionalHeaders({ ':status': 102 });
assert.strictEqual(stream.sentInfoHeaders[0][':status'], 102);

stream.respond({ abc: 'xyz' }, {
getTrailers(headers) {
headers.xyz = 'abc';
}
});
assert.strictEqual(stream.sentHeaders.abc, 'xyz');
assert.strictEqual(stream.sentHeaders[':status'], 200);
assert.notStrictEqual(stream.sentHeaders.date, undefined);
stream.end();
stream.on('close', () => {
assert.strictEqual(stream.sentTrailers.xyz, 'abc');
});
}));

server.listen(0, common.mustCall(() => {
const client = h2.connect(`http://localhost:${server.address().port}`);
const req = client.request();

req.on('headers', common.mustCall((headers) => {
assert.strictEqual(headers[':status'], 102);
}));

assert.strictEqual(req.sentHeaders[':method'], 'GET');
assert.strictEqual(req.sentHeaders[':authority'],
`localhost:${server.address().port}`);
assert.strictEqual(req.sentHeaders[':scheme'], 'http');
assert.strictEqual(req.sentHeaders[':path'], '/');
req.resume();
req.on('close', () => {
server.close();
client.close();
});
}));