-
Notifications
You must be signed in to change notification settings - Fork 477
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
Api gateway response parsing #1903
Api gateway response parsing #1903
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API Gateway sets common headers on the response. Like a request id. We should mimic those as much as possible.
/// </summary> | ||
/// <param name="apiResponse">The API Gateway proxy response to convert.</param> | ||
/// <returns>An HttpResponse object representing the API Gateway response.</returns> | ||
public static HttpResponse ToHttpResponse(this APIGatewayProxyResponse apiResponse) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point I think here we need to do a size check that we aren't returning more then 6MB. And if there is an attempt return back the same error that API Gateway would do.
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
{ | ||
foreach (var header in headers) | ||
{ | ||
response.Headers[header.Key] = header.Value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could be dealing with a IDictionary<string, string>
collection that has comma delimited multi value headers. That needs to be handled here. I think this code will create a single header in API Gateway with the comma delimited string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lambda code
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'myheader': 'test,other',
'anotherheader': 'firstvalue',
'anotherheader': 'secondvalue'
},
multiValueHeaders: {
"headername": ["headervalue", "headervalue2"]
},
body: JSON.stringify({
event
}), };
};
V1 Response headers
Myheader: test,shouldhavesecondvalue
Anotherheader: secondvalue
Headername: headervalue
Headername: headervalue2
v2 response headers
Anotherheader: secondvalue
Myheader: test,shouldhavesecondvalue
however, it does look like im missing one case, where headers and multivalueheaders are both defined for the same key, it should give me the combined result of all of them for v1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i will add a test case to verify
|
||
response.StatusCode = apiResponse.StatusCode; | ||
|
||
SetResponseHeaders(response, apiResponse.Headers, apiResponse.MultiValueHeaders); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure if Content-Type is not returned by the Lambda function API Gateway will default to application/json. It might also set the content-length. We need to mimic that behavior but confirm my assumption.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i updated to set content length. for the default content-type, theres actually a couple of cases for how that can get set. ive written test cases for those and it may be easier to read them than me typing :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so i dug more into this. this was tripping me up for a couple of hours. the default content type actually depends on what kind of api gateway is being used. If content-type is not specified and a REST api is being used, the content type is application/.json. but if its an http api being used (for v1) its text/plain. but then a http (v2) can sometimes infer the application type is json...
|
||
response.StatusCode = apiResponse.StatusCode; | ||
|
||
SetResponseHeaders(response, apiResponse.Headers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure if Content-Type is not returned by the Lambda function API Gateway will default to application/json. It might also set the content-length. We need to mimic that behavior but confirm my assumption.
56af10b
to
8d33c54
Compare
Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayTestHelper.cs
Outdated
Show resolved
Hide resolved
...ol-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseExtensionsTests.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
...v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayResponseExtensionsSpecialCases.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayTestHelper.cs
Outdated
Show resolved
Hide resolved
090e360
to
22eb253
Compare
/// <param name="headers">The single-value headers to set.</param> | ||
/// <param name="multiValueHeaders">The multi-value headers to set.</param> | ||
/// <param name="emulatorMode">The API Gateway emulator mode determining which default headers to include.</param> | ||
private static void SetResponseHeaders(HttpResponse response, IDictionary<string, string>? headers, IDictionary<string, IList<string>>? multiValueHeaders = null, ApiGatewayEmulatorMode emulatorMode = ApiGatewayEmulatorMode.HttpV2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a default for emulatorMode
seems odd to me. Defaulting the value to HttpV2 seems random.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in latest revision
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
var errorBytes = Encoding.UTF8.GetBytes("{\"message\": \"Internal server error\"}"); | ||
response.Body = new MemoryStream(errorBytes); | ||
response.ContentLength = errorBytes.Length; | ||
} else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Please put the else
on a separate line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in latest revision
{ | ||
// API Gateway 2.0 format version assumptions | ||
response.StatusCode = 200; | ||
response.ContentType = "application/json"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should you check to see if content type was already set from the headers collection coming back from the Lambda function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ill add an integration test case to confirm what happens in that scenario and update accordingly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ive added V2_TestJsonInfersAlwaysUsesApplicationJson
. i believe my current logic is right. the reason being is for this scenario, since status code is 0, api gateway just uses the whole payload as the body response and doesnt consider any lambda provided headers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i updated this logic to just be straightforward - with the results from my manual testing. that if status code is not set, then api gateway http 2.0 will default to application/json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess what you meant by this (based on your other comment with the binary type for rest api), is that if the user configured custom response headers for this error code that it might be in the response already? ill have to check that
edit: actually i guess my above crossed out comment only applies to the other code up above where i do internal server error. so ill see what happens in that case. i think the user can specify custom response headers in rest api depending on certain error codes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so i was thinking about this more with the way the v2 infers things. i think we need another layer before this api gateway response -> http response. i think we need a lambda memory stream -> api gateway response, which will do these inferences
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i removed the json inference check from here. i think we should add the lambda -> api gateay translation inference in another pr
...2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayResponseExtensionsJsonInference.cs
Outdated
Show resolved
Hide resolved
_httpClient = new HttpClient(); | ||
} | ||
|
||
public async Task<string> CreateLambdaFunctionAsync(string roleArn, string lambdaCode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please create the function with a CloudFormation template so it is easier to tear down when we have orphan test data when tests crash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how would a cloud formation template for the lambda be easier to tear down for the lambda exactly? edit: i talked with phil. since it would all be bundled together it would be easier to just delete the stack
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would like to update it to use cloudformation in a separate PR, after i get the main functionality in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, i figured out how to do this. i will push an update using cloudformation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ive updated it to use cloudformation to create everything. i reran the integration tests too and they all pass (except for the 1 i have intentionally failing right now related to the v2 json assumptions)
...ol-v2/tests/Amazon.Lambda.TestTool.UnitTests/Extensions/ApiGatewayResponseExtensionsTests.cs
Outdated
Show resolved
Hide resolved
22eb253
to
d0707ad
Compare
@normj we can discuss more on 12/18 on the application/json default logic when the status code is 0 for http v2. i feel that is the last big thing functionality wise missing. so far i have been able to test when the status code is 0 and the body is valid json. but i have not been able to figure out how to verify what happens when the status code is 0 and the body is not valid json |
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ApiGatewayResponseExtensions.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ServiceCollectionExtensions.cs
Outdated
Show resolved
Hide resolved
...tests/Amazon.Lambda.TestTool.IntegrationTests/Amazon.Lambda.TestTool.IntegrationTests.csproj
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ServiceCollectionExtensions.cs
Outdated
Show resolved
Hide resolved
...2/tests/Amazon.Lambda.TestTool.IntegrationTests/ApiGatewayResponseExtensionsJsonInference.cs
Outdated
Show resolved
Hide resolved
2887025
to
a374bd5
Compare
if (!string.IsNullOrEmpty(body)) | ||
{ | ||
byte[] bodyBytes; | ||
if (isBase64Encoded && ApiGatewayEmulatorMode.Rest != apiGatewayEmulator) // rest api gateway doesnt automatically decode the response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If isBase64Encoded
is true
we should always decode the body. I think what you are seeing is that in API Gateway you need to configure your REST API for binary media. And then it will decode it. From our emulator's point of view we should assume that they have correctly configured binary media and so we should decode base 64 encoded bodies.
In CloudFormation you can configure binary media by adding the following resource:
"TheApi": {
"Type": "AWS::Serverless::Api",
"Properties": {
"StageName": "Prod",
"BinaryMediaTypes": [
"*~1*"
]
}
},
Then on the event configuration for the serviceless function reference the API:
],
"PackageType": "Zip",
"Events": {
"RootGet": {
"Type": "Api",
"Properties": {
"Path": "/",
"Method": "GET",
"RestApiId" : { "Ref" : "TheApi" }
}
}
``
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i see. that certainly makes more sense. ill test it out to see what happens
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah i just tested this and you are right. i will update this logic and add a test case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ive fixed this now and have also added an integration and unit test
namespace Amazon.Lambda.TestTool.IntegrationTests | ||
{ | ||
[Collection("ApiGateway Integration Tests")] | ||
public class ApiGatewayResponseExtensionsTestsManual |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does the Manual
suffix mean here? This test will run automatically as part of integration test runs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ill rename this to something better. its not part of the ApiGatewayResponeTestCases list and the test cases are separate. maybe AdditionalTests
|
||
[Theory] | ||
[MemberData(nameof(ApiGatewayResponseTestCases.V1TestCases), MemberType = typeof(ApiGatewayResponseTestCases))] | ||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i added this suppression so in vscode we can easily see what test is being executed by having test name parameter here
9451d3c
to
7e88575
Compare
} | ||
|
||
//[Fact] | ||
//public async Task V2_SetsContentTypeApplicationJsonWhenNoStatusProvided() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i commented this out for now so it can be referenced for future when we create the lambda -> api gateway response layer
Issue #, if available:
DOTNET-7839
Description of changes:
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.