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

util.normalizeHttpResponse to assume statusCode=200 when missing #874

Closed
velkoborsky opened this issue Jul 10, 2022 · 5 comments
Closed
Assignees
Milestone

Comments

@velkoborsky
Copy link

velkoborsky commented Jul 10, 2022

Middy util contains a normalizeHttpResponse method, which has a behavior that seems potentially confusing and inconsistent with "native" AWS Labmda - AWS Gateway integration.

The function of the normalizeHttpResponse method is, in short: when the response is a simple object or a string, encapsulate it into a body attribute of a structured response.

    response = { body: response } // line 204

That is similar to what AWS Gateway does natively when it gets a string or an object as a response - it uses the object/string as a body, returning it with a 200 status code, as described in the documentation.

However, the Middy normalization causes a strange / unintuitive behavior. On an example of a simple lambda returning {"hello": "world"}, when it is called by AWS Gateway directly, client indeed receives '{"hello":"world"}' as expected, however when Middy with some middleware using normalization is used (e.g. httpSecurityHeadersMiddleware), client receives an unexpected response such as: '{"body":{"hello":"world"},"headers":"..."}'.

I believe this is almost never the intention (e.g. the security headers here do not work) and the behavior would be more reasonable and more consistent with native API Gateway experience if the normalizeHttpResponse also made the same "statusCode=200 if missing" assumption as the AWS Gateway does. I.e. in its source add:

  response.headers ??= {} // line 206

  response.statusCode ??= 200 // NEW

  request.response = response // line 207

Sorry if I am missing some case where the current behavior is required.

Thank you for Middy.

@velkoborsky
Copy link
Author

velkoborsky commented Jul 10, 2022

As a possible workaround, if anyone else encounters the same issue, we are using a micro-middleware, which makes this behavior explicit. Full source:

import middy from '@middy/core';
import { normalizeHttpResponse } from '@middy/util';
import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';

type ResponseStatusCodeMiddlewareConfig = {
  statusCode: number;
};

const defaults: ResponseStatusCodeMiddlewareConfig = {
  statusCode: 200,
};

type MiddlewareFn = middy.MiddlewareFn<APIGatewayProxyEventV2, APIGatewayProxyResultV2>;

/**
 * Add a success status code to a response
 */
const responseStatusCodeMiddleware = (
  opts: Partial<ResponseStatusCodeMiddlewareConfig> = {},
): middy.MiddlewareObj<APIGatewayProxyEventV2, APIGatewayProxyResultV2> => {
  const config = { ...defaults, ...opts };

  const after: MiddlewareFn = async (request): Promise<void> => {
    normalizeHttpResponse(request);
    if (request.response && typeof request.response === 'object') request.response.statusCode ??= config.statusCode;
  };

  return { after };
};

export default responseStatusCodeMiddleware;

Usage is then straightforward. For the 200 status:

.use(responseStatusCodeMiddleware())

or for a different one (this use case would not be covered by the proposal above):

.use(responseStatusCodeMiddleware({ statusCode: 201 }))

@willfarrell willfarrell added this to the v4.0.0 milestone Jul 10, 2022
@willfarrell
Copy link
Member

Thanks for the detailed report. normalizeHttpResponse is also used to normalize error responses as well, so it's not that simple. However, in that case statusCode should always be set.

Option A:

response = { statusCode: 200, body: response } // line 204

I would consider this to be safe and inline with AWS docs and would be a non-breaking change. Would this address your use case?

Option B:

response.statusCode ??= 200 // line 206

Could have unexpected consequences for others. I feel 500 would be more appropriate over 200 because of this. Would be considered a breaking change.

@willfarrell willfarrell removed this from the v4.0.0 milestone Jul 10, 2022
@velkoborsky
Copy link
Author

Indeed, your option A, with 200 on line 204 and possibly 500 between lines 206-207 not only solves the original issue, but is also a superior solution to what I was proposing. For now I am using the workaround above but I believe it would be worth including in the future major release. Thank you.

@willfarrell willfarrell added this to the v4.0.0 milestone Jul 10, 2022
@willfarrell willfarrell self-assigned this Jul 10, 2022
@willfarrell
Copy link
Member

I've started a draft for v4. I think this is what we landed on: ebd59e5#diff-ff075fc97631986a9236dd1e4f9a06cff63228e65b3a8b7922eb5b36f23d0002

@velkoborsky
Copy link
Author

Perfect, thank you for solving this.

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

No branches or pull requests

2 participants