Skip to content
This repository was archived by the owner on Jul 6, 2018. It is now read-only.

Commit 28250cd

Browse files
committed
http2: ServerHttp2Stream.headersSent property, proxied by Http2ServerResponse
This avoids brittle abstraction-leaking to guess whether or not ServerHttp2Stream.respond() may still be called on HEAD requests where the stream is ended before headers are sent.
1 parent 08e75c1 commit 28250cd

File tree

3 files changed

+63
-20
lines changed

3 files changed

+63
-20
lines changed

lib/internal/http2/compat.js

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,6 @@ class Http2ServerResponse extends Stream {
262262
this[kState] = {
263263
sendDate: true,
264264
statusCode: constants.HTTP_STATUS_OK,
265-
headersSent: false,
266265
headerCount: 0,
267266
trailerCount: 0,
268267
closed: false,
@@ -300,8 +299,8 @@ class Http2ServerResponse extends Stream {
300299
}
301300

302301
get headersSent() {
303-
var state = this[kState];
304-
return state.headersSent;
302+
var stream = this[kStream];
303+
return stream.headersSent;
305304
}
306305

307306
get sendDate() {
@@ -388,7 +387,7 @@ class Http2ServerResponse extends Stream {
388387
}
389388

390389
flushHeaders() {
391-
if (this[kState].headersSent === false)
390+
if (this[kStream].headersSent === false)
392391
this[kBeginSend]();
393392
}
394393

@@ -480,19 +479,17 @@ class Http2ServerResponse extends Stream {
480479
}
481480

482481
sendInfo(code, headers) {
483-
var state = this[kState];
484-
if (state.headersSent === true) {
482+
var stream = this[kStream];
483+
if (stream.headersSent === true) {
485484
throw new errors.Error('ERR_HTTP2_INFO_HEADERS_AFTER_RESPOND');
486485
}
487486
if (headers && typeof headers !== 'object')
488487
throw new errors.TypeError('ERR_HTTP2_HEADERS_OBJECT');
489-
var stream = this[kStream];
490488
if (stream === undefined) return;
491489
code |= 0;
492490
if (code < 100 || code >= 200)
493491
throw new errors.RangeError('ERR_HTTP2_INVALID_INFO_STATUS', code);
494492

495-
state.headersSent = true;
496493
headers[constants.HTTP2_HEADER_STATUS] = code;
497494
stream.respond(headers);
498495
}
@@ -509,17 +506,14 @@ class Http2ServerResponse extends Stream {
509506
}
510507

511508
[kBeginSend](options) {
512-
var state = this[kState];
513-
var stream = this[kStream];
514-
if (state.headersSent === false) {
515-
state.headersSent = true;
509+
const stream = this[kStream];
510+
if (stream !== undefined && stream.headersSent === false) {
511+
const state = this[kState];
516512
const headers = this[kHeaders] || Object.create(null);
517513
headers[constants.HTTP2_HEADER_STATUS] = state.statusCode;
518-
var _writableState = stream._writableState;
519-
if (stream.destroyed === false &&
520-
_writableState.ending === false &&
521-
_writableState.finished === false
522-
) {
514+
if (stream.finished === true)
515+
options.endStream = true;
516+
if (stream.destroyed === false) {
523517
stream.respond(headers, options);
524518
}
525519
}

lib/internal/http2/core.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,11 @@ class ServerHttp2Stream extends Http2Stream {
15341534
debug(`[${sessionName(session[kType])}] created serverhttp2stream`);
15351535
}
15361536

1537+
// true if the HEADERS frame has been sent
1538+
get headersSent() {
1539+
return this[kState].headersSent;
1540+
}
1541+
15371542
// true if the remote peer accepts push streams
15381543
get pushAllowed() {
15391544
return this[kSession].remoteSettings.enablePush;

test/parallel/test-http2-compat-serverresponse-end.js

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
// Flags: --expose-http2
22
'use strict';
33

4+
const { strictEqual } = require('assert');
45
const { mustCall, mustNotCall } = require('../common');
5-
const { createServer, connect } = require('http2');
6-
7-
// Http2ServerResponse.end
6+
const {
7+
createServer,
8+
connect,
9+
constants: {
10+
HTTP2_HEADER_STATUS,
11+
HTTP_STATUS_OK
12+
}
13+
} = require('http2');
814

915
{
16+
// Http2ServerResponse.end callback is called only the first time,
17+
// but may be invoked repeatedly without throwing errors.
1018
const server = createServer(mustCall((request, response) => {
1119
response.end(mustCall(() => {
1220
server.close();
@@ -31,3 +39,39 @@ const { createServer, connect } = require('http2');
3139
}));
3240
}));
3341
}
42+
43+
{
44+
// Http2ServerResponse.end is not necessary on HEAD requests since the stream
45+
// is already closed. Headers, however, can still be sent to the client.
46+
const server = createServer(mustCall((request, response) => {
47+
strictEqual(response.finished, true);
48+
response.writeHead(HTTP_STATUS_OK, {foo: 'bar'});
49+
response.flushHeaders();
50+
response.end(mustNotCall());
51+
}));
52+
server.listen(0, mustCall(() => {
53+
const {port} = server.address();
54+
const url = `http://localhost:${port}`;
55+
const client = connect(url, mustCall(() => {
56+
const headers = {
57+
':path': '/',
58+
':method': 'HEAD',
59+
':scheme': 'http',
60+
':authority': `localhost:${port}`
61+
};
62+
const request = client.request(headers);
63+
request.on('response', mustCall((headers, flags) => {
64+
strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK);
65+
strictEqual(flags, 5); // the end of stream flag is set
66+
strictEqual(headers.foo, 'bar');
67+
}));
68+
request.on('data', mustNotCall());
69+
request.on('end', mustCall(() => {
70+
client.destroy();
71+
server.close();
72+
}));
73+
request.end();
74+
request.resume();
75+
}));
76+
}));
77+
}

0 commit comments

Comments
 (0)