Skip to content

Commit

Permalink
fix(node-http-handler): throw meaningful errors in H2 events (#2568)
Browse files Browse the repository at this point in the history
  • Loading branch information
AllanZhengYP authored Jul 16, 2021
1 parent 08c0342 commit 160aeba
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 3 deletions.
48 changes: 48 additions & 0 deletions packages/node-http-handler/src/node-http2-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AbortController } from "@aws-sdk/abort-controller";
import { HttpRequest } from "@aws-sdk/protocol-http";
import { rejects } from "assert";
import http2, { constants, Http2Stream } from "http2";
import { Duplex } from "stream";

import { NodeHttp2Handler } from "./node-http2-handler";
import { createMockHttp2Server, createResponseFunction, createResponseFunctionWithDelay } from "./server.mock";
Expand Down Expand Up @@ -375,6 +376,53 @@ describe(NodeHttp2Handler.name, () => {
});
});

it("will throw reasonable error when connection aborted abnormally", async () => {
nodeH2Handler = new NodeHttp2Handler();
// Create a session by sending a request.
await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {});
const authority = `${protocol}//${hostname}:${port}`;
// @ts-ignore: access private property
const session: ClientHttp2Session = nodeH2Handler.sessionCache.get(authority)[0];
const fakeStream = new Duplex();
const fakeRstCode = 1;
// @ts-ignore: fake result code
fakeStream.rstCode = fakeRstCode;
jest.spyOn(session, "request").mockImplementation(() => fakeStream);
// @ts-ignore: access private property
nodeH2Handler.sessionCache.set(`${protocol}//${hostname}:${port}`, [session]);
// Delay response so that onabort is called earlier
setTimeout(() => {
fakeStream.emit("aborted");
}, 0);

await expect(nodeH2Handler.handle(new HttpRequest({ ...getMockReqOptions() }), {})).rejects.toHaveProperty(
"message",
`HTTP/2 stream is abnormally aborted in mid-communication with result code ${fakeRstCode}.`
);
});

it("will throw reasonable error when frameError is thrown", async () => {
nodeH2Handler = new NodeHttp2Handler();
// Create a session by sending a request.
await nodeH2Handler.handle(new HttpRequest(getMockReqOptions()), {});
const authority = `${protocol}//${hostname}:${port}`;
// @ts-ignore: access private property
const session: ClientHttp2Session = nodeH2Handler.sessionCache.get(authority)[0];
const fakeStream = new Duplex();
jest.spyOn(session, "request").mockImplementation(() => fakeStream);
// @ts-ignore: access private property
nodeH2Handler.sessionCache.set(`${protocol}//${hostname}:${port}`, [session]);
// Delay response so that onabort is called earlier
setTimeout(() => {
fakeStream.emit("frameError", "TYPE", "CODE", "ID");
}, 0);

await expect(nodeH2Handler.handle(new HttpRequest({ ...getMockReqOptions() }), {})).rejects.toHaveProperty(
"message",
`Frame type id TYPE in stream id ID has failed with code CODE.`
);
});

describe("disableConcurrentStreams", () => {
beforeEach(() => {
nodeH2Handler = new NodeHttp2Handler({
Expand Down
9 changes: 6 additions & 3 deletions packages/node-http-handler/src/node-http2-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,13 @@ export class NodeHttp2Handler implements HttpHandler {
}

// Set up handlers for errors
req.on("frameError", reject);
req.on("frameError", (type: number, code: number, id: number) => {
reject(new Error(`Frame type id ${type} in stream id ${id} has failed with code ${code}.`));
});
req.on("error", reject);
req.on("goaway", reject);
req.on("aborted", reject);
req.on("aborted", () => {
reject(new Error(`HTTP/2 stream is abnormally aborted in mid-communication with result code ${req.rstCode}.`));
});

// The HTTP/2 error code used when closing the stream can be retrieved using the
// http2stream.rstCode property. If the code is any value other than NGHTTP2_NO_ERROR (0),
Expand Down

0 comments on commit 160aeba

Please sign in to comment.