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: close stream and session on frameError #42147

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/internal/http2/core.js
Expand Up @@ -607,6 +607,8 @@ function onFrameError(id, type, code) {
const emitter = session[kState].streams.get(id) || session;
emitter[kUpdateTimer]();
emitter.emit('frameError', type, code, id);
session[kState].streams.get(id).close(code);
session.close();
}

function onAltSvc(stream, origin, alt) {
Expand Down
23 changes: 22 additions & 1 deletion src/node_http2.cc
Expand Up @@ -1024,6 +1024,27 @@ void Http2Session::DecrefHeaders(const nghttp2_frame* frame) {
}
}

uint32_t TranslateNghttp2ErrorCode(const int libErrorCode) {
switch (libErrorCode) {
case NGHTTP2_ERR_STREAM_CLOSED:
return NGHTTP2_STREAM_CLOSED;
case NGHTTP2_ERR_HEADER_COMP:
return NGHTTP2_COMPRESSION_ERROR;
case NGHTTP2_ERR_FRAME_SIZE_ERROR:
return NGHTTP2_FRAME_SIZE_ERROR;
case NGHTTP2_ERR_FLOW_CONTROL:
return NGHTTP2_FLOW_CONTROL_ERROR;
case NGHTTP2_ERR_REFUSED_STREAM:
return NGHTTP2_REFUSED_STREAM;
case NGHTTP2_ERR_PROTO:
case NGHTTP2_ERR_HTTP_HEADER:
case NGHTTP2_ERR_HTTP_MESSAGING:
return NGHTTP2_PROTOCOL_ERROR;
default:
return NGHTTP2_INTERNAL_ERROR;
}
}

// If nghttp2 is unable to send a queued up frame, it will call this callback
// to let us know. If the failure occurred because we are in the process of
// closing down the session or stream, we go ahead and ignore it. We don't
Expand Down Expand Up @@ -1060,7 +1081,7 @@ int Http2Session::OnFrameNotSent(nghttp2_session* handle,
Local<Value> argv[3] = {
Integer::New(isolate, frame->hd.stream_id),
Integer::New(isolate, frame->hd.type),
Integer::New(isolate, error_code)
Integer::New(isolate, TranslateNghttp2ErrorCode(error_code))
};
session->MakeCallback(
env->http2session_on_frame_error_function(),
Expand Down
51 changes: 51 additions & 0 deletions test/parallel/test-http2-exceeds-server-trailer-size.js
@@ -0,0 +1,51 @@
'use strict';

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

const assert = require('assert');
const { createServer, constants, connect } = require('http2');

const server = createServer();

server.on('stream', (stream, headers) => {
stream.respond(undefined, { waitForTrailers: true });

stream.on('data', common.mustNotCall());

stream.on('wantTrailers', common.mustCall(() => {
// Trigger a frame error by sending a trailer that is too large
stream.sendTrailers({ 'test-trailer': 'X'.repeat(64 * 1024) });
}));

stream.on('frameError', common.mustCall((frameType, errorCode) => {
assert.strictEqual(errorCode, constants.NGHTTP2_FRAME_SIZE_ERROR);
}));

stream.on('error', common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
}));

stream.on('close', common.mustCall());

stream.end();
});

server.listen(0, () => {
const clientSession = connect(`http://localhost:${server.address().port}`);

clientSession.on('frameError', common.mustNotCall());
clientSession.on('close', common.mustCall(() => {
server.close();
}));

const clientStream = clientSession.request();

clientStream.on('close', common.mustCall());
// These events mustn't be called once the frame size error is from the server
clientStream.on('frameError', common.mustNotCall());
clientStream.on('error', common.mustNotCall());

clientStream.end();
});
4 changes: 2 additions & 2 deletions test/parallel/test-http2-options-max-headers-block-length.js
Expand Up @@ -32,12 +32,12 @@ server.listen(0, common.mustCall(() => {
}));

req.on('frameError', common.mustCall((type, code) => {
assert.strictEqual(code, h2.constants.NGHTTP2_ERR_FRAME_SIZE_ERROR);
assert.strictEqual(code, h2.constants.NGHTTP2_FRAME_SIZE_ERROR);
}));

req.on('error', common.expectsError({
code: 'ERR_HTTP2_STREAM_ERROR',
name: 'Error',
message: 'Stream closed with error code NGHTTP2_REFUSED_STREAM'
message: 'Stream closed with error code NGHTTP2_FRAME_SIZE_ERROR'
}));
}));