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

fix(apigateway): duplicate methodResponses if the same array is reused between addMethod calls #26636

Merged
merged 11 commits into from
Aug 17, 2023

Conversation

lpizzinidev
Copy link
Contributor

@lpizzinidev lpizzinidev commented Aug 4, 2023

Adding a new method to an API with addMethod and passing methodOptions.methodResponses was generating duplicate entries using StepFunctionsIntegration.startExecution as integration:

For example:

const integ = apigw.StepFunctionsIntegration.startExecution(stateMachine, integrationOptions);
api.root.addMethod('GET', integ, methodOptions);
api.root.addMethod('POST', integ, methodOptions);

Would generate (fails on deployment):

 "MethodResponses": [
     {
      "ResponseParameters": {
       "method.response.header.Access-Control-Allow-Origin": true
      },
      "StatusCode": "200"
     },
     {
      "ResponseModels": {
       "application/json": "Empty"
      },
      "StatusCode": "200"
     },
     {
      "ResponseModels": {
       "application/json": "Error"
      },
      "StatusCode": "400"
     },
     {
      "ResponseModels": {
       "application/json": "Error"
      },
      "StatusCode": "500"
     },
     {
      "ResponseModels": {
       "application/json": "Empty"
      },
      "StatusCode": "200"
     },
     {
      "ResponseModels": {
       "application/json": "Error"
      },
      "StatusCode": "400"
     },
     {
      "ResponseModels": {
       "application/json": "Error"
      },
      "StatusCode": "500"
     }
    ],

With this fix, it will keep only the specified methodResponses.

Also, the integrationResponses option in StepFunctionsIntegration.startExecution was not used by the corresponding Integration.
This fix will allow to use the specified option value.

Closes #26586.


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

…esponses and Method methodResponses attributes
@github-actions github-actions bot added bug This issue is a bug. effort/medium Medium work item – several days of effort p1 star-contributor [Pilot] contributed between 25-49 PRs to the CDK labels Aug 4, 2023
@aws-cdk-automation aws-cdk-automation requested a review from a team August 4, 2023 10:49
@aws-cdk-automation aws-cdk-automation added the pr/needs-maintainer-review This PR needs a review from a Core Team Member label Aug 4, 2023
Copy link
Contributor

@rix0rrr rix0rrr left a comment

Choose a reason for hiding this comment

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

What if methodOptions.methodResponses is different between the two calls? Won't ignoring it the second time do something unexpected?

Wouldn't it be better to disallow the second call, and force you to add both methods in a single call?

@lpizzinidev
Copy link
Contributor Author

@rix0rrr
What if instead of falling back to undefined we default to METHOD_RESPONSES in renderMethodResponses?
This would centralize the handling of the variable in the Method class and remove the need for populating the array on binding (which to me looks like the source of the problem).
And this should still work.

@lpizzinidev lpizzinidev changed the title fix(apigateway): fixed StepFunctionsExecutionIntegration integrationResponses and Method methodResponses attributes fix(apigateway): the integrationResponses integration option is ignored and passing methodOptions.methodResponses to multiple addMethod calls fails for duplicate values Aug 7, 2023
@rix0rrr
Copy link
Contributor

rix0rrr commented Aug 10, 2023

I didn't understand why this would be happening because addMethod('GET', ...) and addMethod('POST', ...) should be adding 2 different Methods, each with their own MethodResponses.

So I added more validation and as far as I can tell, we also have duplicate method responses even with a single integration?

I have pushed my changes to this branch so you can see. Am I misunderstanding the API Gateway contract, or something else screwy going on?

@rix0rrr
Copy link
Contributor

rix0rrr commented Aug 10, 2023

For example, I'm now seeing this error in a bunch of places:

[aws-cdk-aws-apigateway-stepfunctions-startexecution/my-rest-api/Default/GET] 3 methodResponses for code 200:
{"statusCode":"200","responseParameters":{"method.response.header.Access-Control-Allow-Origin":true}},
{"statusCode":"200","responseModels":{"application/json":{"modelId":"Empty"}}},
{"statusCode":"200","responseModels":{"application/json":{"modelId":"Empty"}}}

Is this expected?

@rix0rrr
Copy link
Contributor

rix0rrr commented Aug 10, 2023

Uh oh. It seems like the CloudFormation should have been rejecting this:

[
{"statusCode":"200","responseParameters":{"method.response.header.Access-Control-Allow-Origin":true}},
{"statusCode":"200","responseModels":{"application/json":{"modelId":"Empty"}}},
]

But didn't. But it does throw an error on duplicate entries in the array, because it's trying to convert them to a set and throws an error if that conversion is lossy.

@rix0rrr
Copy link
Contributor

rix0rrr commented Aug 11, 2023

After looking into it a bit more, something entirely different is happening.

In the code example, the methodOptions argument is reused for both StartExecutions. Then inside the Method constructor, we assign the array from the options to become the private member:

this.methodResponses = options.methodResponses ?? defaultMethodOptions.methodResponses ?? [];

Since arrays are reference values, this leads to object sharing! Both the GET and POST method refer to the exact same array, and so the integration being executed twice adds the same elements to the same array, twice.

Had the code been written like this:

api.root.addMethod('POST', integration, {
  methodResponses: [ ... ],
});
api.root.addMethod('GET', integration, {
  methodResponses: [ ... ],
});

Both methods would have used a different methodResponses array, and the duplicate adding of identical elements would not have occurred.


That's the immediate problem. But there is another problem, that I alluded to before. If there are multiple methodResponses for the same statusCode, CloudFormation will apply them one by one and you'll end up with one of them, arbitrarily. CloudFormation only detects problems when the elements in the array are exactly the same, which happened to happen here.

So already doing the following:

api.root.addMethod('Get', StepFunctionsIntegration.startExecution(machine, integrationOptions), {
  methodResponses: [
    statusCode: '200',
    /* anything here */
  ]
});

Becomes a problem, because:

  • You are adding a methodResponse with statusCode = 200
  • The integration is also adding a methodResponse with statusCode = 200

You now have multiple methodResponses with the same status code, and after deployment your API will end up with one of them, but not both.

So the original use case of passing methodResponses in the method to add a header to all responses isn't even supported.

@lpizzinidev
Copy link
Contributor Author

@rix0rrr

There are 2 problems here:

  1. The addMethod function passes the same reference of options to successive calls if declared as a variable.
    For example, considering this scenario:
    const methodOptions = {
      methodResponses: [
        {
          statusCode: '200',
          responseParameters: {
            'method.response.header.Access-Control-Allow-Origin': true,
          },
        },
      ],
    };

    const integ = apigw.StepFunctionsIntegration.startExecution(stateMachine, integrationOptions);
    api.root.addMethod('GET', integ, methodOptions);
    api.root.addMethod('POST', integ, methodOptions);

The methodResponses of both Methods will reference the same array, causing the duplicates that you mentioned here.

  1. This doesn't take into consideration methods passed via properties, basically overriding in any case, causing the duplicates that you mentioned here

I pushed a tentative solution that should solve both issues.
Let me know if further changes are needed and if this validation should be kept (I will add unit tests if so).

PS
I saw this after pushing the last changes and writing the comment.😄

@rix0rrr rix0rrr changed the title fix(apigateway): the integrationResponses integration option is ignored and passing methodOptions.methodResponses to multiple addMethod calls fails for duplicate values fix(apigateway): methodResponses are accidentally duplicated Aug 11, 2023
@rix0rrr rix0rrr changed the title fix(apigateway): methodResponses are accidentally duplicated fix(apigateway): methodResponses can be duplicated Aug 11, 2023
@rix0rrr
Copy link
Contributor

rix0rrr commented Aug 11, 2023

I'm going to split this into 2 PRs:

  • One for the false sharing
  • One for the response merging (only applies to the stepfunctions integration, because that's the only integration that calls addMethodResponse).

After lunch 😉

@rix0rrr rix0rrr changed the title fix(apigateway): methodResponses can be duplicated fix(apigateway): duplicate methodResponses due to false sharing Aug 11, 2023
@rix0rrr rix0rrr changed the title fix(apigateway): duplicate methodResponses due to false sharing fix(apigateway): duplicate methodResponses if the same array is used between addMethod calls Aug 11, 2023
@rix0rrr rix0rrr changed the title fix(apigateway): duplicate methodResponses if the same array is used between addMethod calls fix(apigateway): duplicate methodResponses if the same array is reused between addMethod calls Aug 11, 2023
rix0rrr
rix0rrr previously approved these changes Aug 11, 2023
@mergify mergify bot dismissed rix0rrr’s stale review August 11, 2023 13:31

Pull request has been modified.

rix0rrr
rix0rrr previously approved these changes Aug 11, 2023
@mergify mergify bot dismissed rix0rrr’s stale review August 14, 2023 09:45

Pull request has been modified.

rix0rrr
rix0rrr previously approved these changes Aug 14, 2023
@mergify
Copy link
Contributor

mergify bot commented Aug 14, 2023

Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@aws-cdk-automation aws-cdk-automation removed the pr/needs-maintainer-review This PR needs a review from a Core Team Member label Aug 14, 2023
@mergify
Copy link
Contributor

mergify bot commented Aug 14, 2023

Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify mergify bot dismissed rix0rrr’s stale review August 17, 2023 11:23

Pull request has been modified.

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildv2Project1C6BFA3F-wQm2hXv2jqQv
  • Commit ID: d18700f
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify
Copy link
Contributor

mergify bot commented Aug 17, 2023

Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify mergify bot merged commit 8dc5190 into aws:main Aug 17, 2023
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. effort/medium Medium work item – several days of effort p1 star-contributor [Pilot] contributed between 25-49 PRs to the CDK
Projects
None yet
3 participants