Skip to content

fix(event-handler): ignore body on GET/HEAD requests when converting to Web Request - #5683

Merged
svozza merged 4 commits into
aws-powertools:mainfrom
sabaighani:fix/get-head-body-handling
Sep 9, 2026
Merged

svozza merged 4 commits into
aws-powertools:mainfrom
sabaighani:fix/get-head-body-handling

Conversation

@sabaighani

@sabaighani sabaighani commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Changes

API Gateway v1/v2 events can include a body on GET or HEAD requests, even
though that's unusual. The V1 and V2 event converters
(proxyEventV1ToWebRequest and proxyEventV2ToWebRequest) passed the body
straight through to the Web Request constructor. Since a Web Request
with method GET/HEAD cannot carry a body, this throws an uncaught
TypeError, crashing the handler invocation and returning a 502 to the
client.

The ALB converter already guarded against this case. This PR moves that
guard into the shared createBody helper (used by all three converters) so
GET/HEAD requests always get a null body, matching the ALB converter's
existing behavior.

Added regression tests covering GET and HEAD requests with a body for both
the API Gateway v1 and v2 converters.

Issue number: closes #5673


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

…to Web Request

API Gateway v1/v2 events can carry a body on GET or HEAD requests. The V1/V2 converters passed it through unconditionally, so the Web Request constructor threw a TypeError (GET/HEAD requests cannot have a body), crashing the handler invocation and returning a 502.

Apply the same guard already used by the ALB converter, moved into the shared createBody helper, and add regression tests for GET/HEAD with a body on both V1 and V2.

Closes aws-powertools#5673

@svozza svozza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up, the shared guard in createBody is the right place for it.

One change I'd like before merging. The dispatcher validates the method on an uppercased copy and then throws it away, and each converter re-reads the raw method from the event:

const method = event.httpMethod.toUpperCase();
if (!isHttpMethod(method)) throw new InvalidHttpMethodError(method);
return proxyEventV1ToWebRequest(event);   // reads event.httpMethod again, raw

That means a lowercase get passes validation but misses the new guard, and Request throws the same TypeError this PR is fixing. patch is worse: the Fetch spec only normalises six methods and PATCH isn't one of them, so it reaches the router as patch and the route lookup misses. Neither happens with real API Gateway or ALB events today, but since you're already changing createBody's signature this is the moment to close it.

Suggestion: normalise at the point the method is read, so the value that's validated is the value that's used.

/**
 * Reads and normalises the HTTP method of an event, throwing when it is not one we route.
 *
 * @param rawMethod - The method as it appears on the event
 */
const toHttpMethod = (rawMethod: string): HttpMethod => {
  const method = rawMethod.toUpperCase();
  if (!isHttpMethod(method)) {
    throw new InvalidHttpMethodError(method);
  }
  return method;
};

Each converter derives its method once from its own event and uses it for both the Request constructor and createBody:

const proxyEventV1ToWebRequest = (event: APIGatewayProxyEvent): Request => {
  const method = toHttpMethod(event.httpMethod);
  // ...
  return new Request(url.toString(), {
    method,
    headers,
    body: createBody(event.body, event.isBase64Encoded, method),
  });
};

Same for V2 (event.requestContext.http.method) and ALB. createBody can then take method: HttpMethod instead of string, and the dispatcher loses its two validation blocks and just dispatches on event type. The InvalidHttpMethodError is still thrown before any Request is built, so the existing 405 handling in Router is unaffected and the CONNECT/TRACE tests should pass as they are.

There's still some repetition left across the three converters after this, but that's beyond the scope of this PR; we're planning a follow-up to tidy that up separately.

Two small test asks:

  • A converter-level case with a lowercase method, asserting get yields a GET request with a null body and patch yields PATCH, since those are the two behaviours that change.
  • One it.each over V1 and V2 in tests/unit/http/Router/basic-routing.test.ts that resolves a GET with a body through app.resolve() and asserts a 200, so the fix is locked in end to end rather than only at the converter.

Move method validation and normalization into a shared toHttpMethod helper called once per converter, so the same normalized value feeds both the Request constructor and createBody.

This closes a gap: a lowercase method (e.g. get) previously passed the dispatcher's uppercase-only validation but missed createBody's guard when the converter re-read the raw method, and a lowercase patch was not normalized by the Fetch spec, so route lookup would miss it.

Adds regression tests for lowercase get and patch on both V1 and V2 converters.

Addresses review feedback from svozza on PR aws-powertools#5683.
@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added size/L PRs between 100-499 LOC and removed size/M PR between 30-99 LOC labels Sep 9, 2026
Adds an it.each over the V1 and V2 converters that resolves a GET request carrying a body through Router.resolve() end to end and asserts a 200, per svozza's second test ask on PR aws-powertools#5683.
@sabaighani

Copy link
Copy Markdown
Contributor Author

Good catch, thanks! Moved the normalization into a shared toHttpMethod helper called once per converter (V1, V2, ALB), so the same normalized value now feeds both the Request constructor and createBody. The dispatcher lost its duplicate validation blocks, and createBody now takes HttpMethod instead of string.

Added both test cases:

  • Converter-level tests for a lowercase get (body correctly ignored) and lowercase patch (normalized to PATCH) on both the V1 and V2 converters.
  • An it.each over V1 and V2 in basic-routing.test.ts that resolves a GET request carrying a body through app.resolve() end to end and asserts a 200.

@svozza
svozza enabled auto-merge (squash) September 9, 2026 20:07
@svozza
svozza merged commit 51bc98c into aws-powertools:main Sep 9, 2026
41 checks passed
@powertools-for-aws-oss-automation

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request and thank you for helping improve everyone's experience!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L PRs between 100-499 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: GET or HEAD request with a body crashes the HTTP event handler invocation

2 participants