http2: remember sent headers

Add sentHeaders, sentTrailers, and sentInfoHeaders properties
on `Http2Stream`.

PR-URL: https://github.com/nodejs/node/pull/18045
Fixes: https://github.com/nodejs/node/issues/16619
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell 2018-01-08 12:18:22 -08:00
parent 20fe04f113
commit ee2e7fcd5f
3 changed files with 99 additions and 0 deletions

View File

@ -989,6 +989,34 @@ destroyed after either receiving an `RST_STREAM` frame from the connected peer,
calling `http2stream.close()`, or `http2stream.destroy()`. Will be calling `http2stream.close()`, or `http2stream.destroy()`. Will be
`undefined` if the `Http2Stream` has not been closed. `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 #### http2stream.session
<!-- YAML <!-- YAML
added: v8.4.0 added: v8.4.0

View File

@ -76,6 +76,7 @@ const kEncrypted = Symbol('encrypted');
const kHandle = Symbol('handle'); const kHandle = Symbol('handle');
const kID = Symbol('id'); const kID = Symbol('id');
const kInit = Symbol('init'); const kInit = Symbol('init');
const kInfoHeaders = Symbol('sent-info-headers');
const kMaybeDestroy = Symbol('maybe-destroy'); const kMaybeDestroy = Symbol('maybe-destroy');
const kLocalSettings = Symbol('local-settings'); const kLocalSettings = Symbol('local-settings');
const kOptions = Symbol('options'); const kOptions = Symbol('options');
@ -84,6 +85,8 @@ const kProceed = Symbol('proceed');
const kProtocol = Symbol('protocol'); const kProtocol = Symbol('protocol');
const kProxySocket = Symbol('proxy-socket'); const kProxySocket = Symbol('proxy-socket');
const kRemoteSettings = Symbol('remote-settings'); const kRemoteSettings = Symbol('remote-settings');
const kSentHeaders = Symbol('sent-headers');
const kSentTrailers = Symbol('sent-trailers');
const kServer = Symbol('server'); const kServer = Symbol('server');
const kSession = Symbol('session'); const kSession = Symbol('session');
const kState = Symbol('state'); const kState = Symbol('state');
@ -258,6 +261,7 @@ function onStreamTrailers() {
stream.destroy(headersList); stream.destroy(headersList);
return []; return [];
} }
stream[kSentTrailers] = trailers;
return headersList; return headersList;
} }
@ -1348,6 +1352,7 @@ class ClientHttp2Session extends Http2Session {
throw headersList; throw headersList;
const stream = new ClientHttp2Stream(this, undefined, undefined, {}); const stream = new ClientHttp2Stream(this, undefined, undefined, {});
stream[kSentHeaders] = headers;
// Close the writable side of the stream if options.endStream is set. // Close the writable side of the stream if options.endStream is set.
if (options.endStream) if (options.endStream)
@ -1514,6 +1519,18 @@ class Http2Stream extends Duplex {
return `Http2Stream ${util.format(obj)}`; return `Http2Stream ${util.format(obj)}`;
} }
get sentHeaders() {
return this[kSentHeaders];
}
get sentTrailers() {
return this[kSentTrailers];
}
get sentInfoHeaders() {
return this[kInfoHeaders];
}
get pending() { get pending() {
return this[kID] === undefined; return this[kID] === undefined;
} }
@ -1855,6 +1872,7 @@ function processRespondWithFD(self, fd, headers, offset = 0, length = -1,
state.flags |= STREAM_FLAGS_HEADERS_SENT; state.flags |= STREAM_FLAGS_HEADERS_SENT;
const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse); const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
self[kSentHeaders] = headers;
if (!Array.isArray(headersList)) { if (!Array.isArray(headersList)) {
self.destroy(headersList); self.destroy(headersList);
return; return;
@ -2085,6 +2103,7 @@ class ServerHttp2Stream extends Http2Stream {
const id = ret.id(); const id = ret.id();
const stream = new ServerHttp2Stream(session, ret, id, options, headers); const stream = new ServerHttp2Stream(session, ret, id, options, headers);
stream[kSentHeaders] = headers;
if (options.endStream) if (options.endStream)
stream.end(); stream.end();
@ -2144,6 +2163,7 @@ class ServerHttp2Stream extends Http2Stream {
const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse); const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
if (!Array.isArray(headersList)) if (!Array.isArray(headersList))
throw headersList; throw headersList;
this[kSentHeaders] = headers;
state.flags |= STREAM_FLAGS_HEADERS_SENT; state.flags |= STREAM_FLAGS_HEADERS_SENT;
@ -2329,6 +2349,10 @@ class ServerHttp2Stream extends Http2Stream {
const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse); const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
if (!Array.isArray(headersList)) if (!Array.isArray(headersList))
throw headersList; throw headersList;
if (!this[kInfoHeaders])
this[kInfoHeaders] = [headers];
else
this[kInfoHeaders].push(headers);
const ret = this[kHandle].info(headersList); const ret = this[kHandle].info(headersList);
if (ret < 0) if (ret < 0)

View File

@ -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();
});
}));