-
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
Add ApiGatewayHttpApiV2ProxyRequestTranslator and ApiGatewayProxyRequestTranslator #1901
Conversation
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/ApiGatewayHttpApiV2ProxyRequestTranslator.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/ApiGatewayHttpApiV2ProxyRequestTranslator.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/ApiGatewayHttpApiV2ProxyRequestTranslator.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/ApiGatewayHttpApiV2ProxyRequestTranslator.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/ApiGatewayHttpApiV2ProxyRequestTranslator.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/ApiGatewayHttpApiV2ProxyRequestTranslator.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/ApiGatewayProxyRequestTranslator.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/ApiGatewayProxyRequestTranslator.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/ApiGatewayRequestTranslatorFactory.cs
Outdated
Show resolved
Hide resolved
471264d
to
2e94f8b
Compare
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/IRouteConfigurationParser.cs
Outdated
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/HttpContextExtensions.cs
Outdated
Show resolved
Hide resolved
f996e5b
to
77f7679
Compare
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs
Show resolved
Hide resolved
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ExceptionExtensions.cs
Outdated
Show resolved
Hide resolved
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 don't see any tests for special characters in headers, resource paths or query strings. Have we done a comparison yet with API Gateway? We should have tests for those cases. That includes the RawPath
property as well.
so i did some tests of checking the existing functionality of api gateway in production, with special characters in the header by
|
if (string.IsNullOrEmpty(contentType)) | ||
return false; | ||
|
||
return contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) || |
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 have this list of content types we use for response in the ASP.NET Core bridge library. https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs#L59
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 should also think about how this can be configurable. Probably via environment variables that we can have users configure in Aspire.
Headers = headers, | ||
QueryStringParameters = queryStringParameters, | ||
PathParameters = pathParameters ?? new Dictionary<string, string>(), | ||
Body = HttpRequestUtility.ReadRequestBody(request), |
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.
Can you take note that when we get these ToAPIGatewayXXX() methods hooked up to the main code path that is asking for the APIGatewayHttpApiV2ProxyRequest
. When we convert the APIGatewayHttpApiV2ProxyRequest
to the MemoryStream to Lambda we should check the size of the stream is no more then 6MB. If so throw back the same error that users would get from API Gateway.
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 will connect with phil on where he wants to do this logic at
MultiValueHeaders = multiValueHeaders, | ||
QueryStringParameters = queryStringParameters, | ||
MultiValueQueryStringParameters = multiValueQueryStringParameters, | ||
PathParameters = pathParameters ?? new Dictionary<string, 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.
Why are we initializing this to an empty collection if we don't have any. Is this what API Gateway does?
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.
good catch. i just did a test
Invoke-WebRequest -Uri https://myapi-gateway-url.amazonaws.com/testfunction -Method GET
where it echos the api gateway request object and i see the value is null
when there are no path parameters.
pathParameters: null,
stageVariables: null,
body: null,
isBase64Encoded: false
}
i will update the code
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 also double check the query string and header default values
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 the logic to handle this now
|
||
foreach (var header in headers) | ||
{ | ||
singleValueHeaders[header.Key] = header.Value.Last() ?? ""; |
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.
My memory for API Gateway is that in HTTP V2 where there is just a single headers collection the headers are comma delimited. But we need to verify that. I'm sure it isn't just get the last 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.
yeah you are right on this https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
Format 2.0 doesn't have multiValueHeaders or multiValueQueryStringParameters fields. Duplicate headers are combined with commas and included in the headers field. Duplicate query strings are combined with commas and included in the queryStringParameters field.
i think i got confused when reading the other docs for v1 https://aws.amazon.com/blogs/compute/support-for-multi-value-parameters-in-amazon-api-gateway/
Before this change, API Gateway used to retain only the last value and drop everything else for a multi-valued parameter. You can see the original behavior in the queryStringParameters parameter in the above input, where only the “fish” value is retained.
I will 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 updated the logic to handle this now. this logic will still just return the dicts as-is but then i have the caller perform the comma separated merging.
|
||
foreach (var param in query) | ||
{ | ||
singleValueParams[param.Key] = param.Value.Last() ?? ""; |
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.
Same comment as above for ExtractHeaders
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 the logic to handle this now
@gcbeattyAWS Also check more characters then aws-lambda-dotnet/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/Utilities.cs Line 148 in 3596a58
|
so i have updated the logic based on my findings:1
i have added test cases to check all of these scenarios |
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs
Show resolved
Hide resolved
1080c5c
to
1d8f81d
Compare
...ambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/HttpContextExtensionsTests.cs
Show resolved
Hide resolved
pushing what i have so far |
...ambdaTestTool-v2/tests/Amazon.Lambda.TestTool.IntegrationTests/HttpContextExtensionsTests.cs
Show resolved
Hide resolved
var (_, allHeaders) = HttpRequestUtility.ExtractHeaders(request.Headers); | ||
var headers = allHeaders.ToDictionary( | ||
kvp => kvp.Key, | ||
kvp => string.Join(", ", kvp.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.
nit: small inconsistency between headers and query params. Here you are joining on ", " and below on ","
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.
this is intentional. from my testing the headers to have a space in them but query string params do not
|
||
if (!headers.ContainsKey("content-length")) | ||
{ | ||
headers["content-length"] = contentLength.ToString(); |
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.
Shouldn't we always update the content-length since we are updating the body?
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 checked this an apparently rest does not set this by default.
Edit: that was for the v1 actually.
i can still update this to always set it. let me double check
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 checked on this. it needs to be the original content-length which is why i dont need to re-set it. my integration test fails (the real api gateway expects the original content length)
{ | ||
Method = request.Method, | ||
Path = request.Path.Value, // this should be decoded value | ||
Protocol = !string.IsNullOrEmpty(request.Protocol) ? request.Protocol : "HTTP/1.1", // defaults to http 1.1 if not provided |
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 we make the "HTTP/1.1" assumption? This is more likely to get updated and we wouldn't know to update it
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 put this here so it matches api gateway
var (headers, multiValueHeaders) = HttpRequestUtility.ExtractHeaders(request.Headers); | ||
var (queryStringParameters, multiValueQueryStringParameters) = HttpRequestUtility.ExtractQueryStringParameters(request.Query); | ||
|
||
if (!headers.ContainsKey("content-length")) |
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.
Shouldn't we always update the content-length since we are updating the body?
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.
yea thats a good point. it doesnt hurt to always update it
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.
looks like rest api doesnt set this automatically by default
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs
Show resolved
Hide resolved
/// X-Custom-Header: value1 | ||
/// | ||
/// The method will return: | ||
/// singleValueHeaders: { "Accept": "application/xhtml+xml", "X-Custom-Header": "value1" } |
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.
Why are we taking the last occurrence of Accept? Is that a valid 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.
So when api gateway receives a request with headers
Accept: a
Accept: b
the single value header would be Accept: b
However, in .net headers are always sent to api gateway like
Accept: a, b
and singleValue header would be Accept: a, b
.
so depending what we want to do we can either do the intended way by api gateway, or do `, '.join(header.value) for the singelvalue header and mimic what the value would be if coming from .net.
This same issue applies for multivalueheaders
when api gateway receives a request with headers
Accept: a
Accept: b
multi value headers would be `accept: ['a', 'b']
but when sending from .net as
Accept: a, b
multivalueheadeders would be `accept: ['a, b'] (the string)
/// For query string: ?param1=value1&param2=value2&param2=value3 | ||
/// | ||
/// The method will return: | ||
/// singleValueParams: { "param1": "value1", "param2": "value3" } |
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.
same question here
/// The generated ID is a 145character string consisting of lowercase letters and numbers, followed by an equals sign. | ||
public static string GenerateRequestId() | ||
{ | ||
return Guid.NewGuid().ToString("N").Substring(0, 8) + Guid.NewGuid().ToString("N").Substring(0, 7) + "="; |
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.
return Guid.NewGuid().ToString("N").Substring(0, 8) + Guid.NewGuid().ToString("N").Substring(0, 7) + "="; | |
return $"{Guid.NewGuid().ToString("N").Substring(0, 8)} {Guid.NewGuid().ToString("N").Substring(0, 7)}="; |
/// // parameters will contain: { {"id", "123"}, {"orderId", "456"} } | ||
/// </code> | ||
/// </example> | ||
public static Dictionary<string, string> ExtractPathParameters(string routeTemplate, string actualPath) |
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.
This does not take into account the greedy variable {proxy+}
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.
this should be fixed now
/// // defaults will contain: { {"version", "v1"} } | ||
/// </code> | ||
/// </example> | ||
public static RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate) |
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.
This is not needed
1d8f81d
to
356b0cc
Compare
356b0cc
to
d96af57
Compare
} | ||
|
||
[Fact] | ||
public async Task BinaryContentHttpV1() |
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 still need an integration test for the rest api for binary content. it was taking too long to setup though because i think need to create a new lambda, api gateway with binary types configured and all that. i can create a backlog item to add this in next pr
{ | ||
var encodedPathParameters = pathParameters.ToDictionary( | ||
kvp => kvp.Key, | ||
kvp => Uri.EscapeUriString(kvp.Value)); // intentionally using EscapeURiString over EscapeDataString since EscapeURiString correctly handles reserved characters :/?#[]@!$&'()*+,;= in this 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.
for example. escapedatastring encodes * as %20 which is different than how api gateway rest api does it (it keeps * as *)
[Theory] | ||
[InlineData(ApiGatewayEmulatorMode.Rest)] | ||
[InlineData(ApiGatewayEmulatorMode.HttpV1)] | ||
public async Task ToApiGateway_MultiValueHeader(ApiGatewayEmulatorMode emulatorMode) |
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 manually tested these unit tests
@@ -16,17 +16,39 @@ public class ApiGatewayIntegrationTestFixture : IAsyncLifetime | |||
public ApiGatewayTestHelper ApiGatewayTestHelper { get; private set; } | |||
|
|||
public string StackName { get; private set; } | |||
public string RestApiId { get; private set; } |
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.
this is just renaming
DOTNET-7838
Description of changes:
Testing details
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.