Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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