diff --git a/packages/node-http-handler/src/node-http2-handler.spec.ts b/packages/node-http-handler/src/node-http2-handler.spec.ts index dc3168733add..da0f9017f2a3 100644 --- a/packages/node-http-handler/src/node-http2-handler.spec.ts +++ b/packages/node-http-handler/src/node-http2-handler.spec.ts @@ -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"; @@ -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({ diff --git a/packages/node-http-handler/src/node-http2-handler.ts b/packages/node-http-handler/src/node-http2-handler.ts index 4d15cdec524f..6d2afce12b70 100644 --- a/packages/node-http-handler/src/node-http2-handler.ts +++ b/packages/node-http-handler/src/node-http2-handler.ts @@ -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),