-
Notifications
You must be signed in to change notification settings - Fork 477
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add request parsing into api gateway
- Loading branch information
1 parent
1af7cf4
commit 77f7679
Showing
13 changed files
with
676 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 24 additions & 24 deletions
48
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ExceptionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,29 @@ | ||
using Amazon.Lambda.TestTool.Models; | ||
|
||
namespace Amazon.Lambda.TestTool.Extensions; | ||
|
||
using Amazon.Lambda.TestTool.Models; | ||
|
||
namespace Amazon.Lambda.TestTool.Extensions; | ||
|
||
/// <summary> | ||
/// A class that contains extension methods for the <see cref="Exception"/> class. | ||
/// </summary> | ||
public static class ExceptionExtensions | ||
{ | ||
/// <summary> | ||
/// True if the <paramref name="e"/> inherits from | ||
/// <see cref="TestToolException"/>. | ||
/// </summary> | ||
public static bool IsExpectedException(this Exception e) => | ||
e is TestToolException; | ||
|
||
/// </summary> | ||
public static class ExceptionExtensions | ||
{ | ||
/// <summary> | ||
/// True if the <paramref name="e"/> inherits from | ||
/// <see cref="TestToolException"/>. | ||
/// </summary> | ||
public static bool IsExpectedException(this Exception e) => | ||
e is TestToolException; | ||
|
||
/// <summary> | ||
/// Prints an exception in a user-friendly way. | ||
/// </summary> | ||
public static string PrettyPrint(this Exception? e) | ||
{ | ||
if (null == e) | ||
return string.Empty; | ||
|
||
return $"{Environment.NewLine}{e.Message}" + | ||
$"{Environment.NewLine}{e.StackTrace}" + | ||
$"{PrettyPrint(e.InnerException)}"; | ||
} | ||
/// </summary> | ||
public static string PrettyPrint(this Exception? e) | ||
{ | ||
if (null == e) | ||
return string.Empty; | ||
|
||
return $"{Environment.NewLine}{e.Message}" + | ||
$"{Environment.NewLine}{e.StackTrace}" + | ||
$"{PrettyPrint(e.InnerException)}"; | ||
} | ||
} |
104 changes: 104 additions & 0 deletions
104
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/HttpContextExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
namespace Amazon.Lambda.TestTool.Extensions; | ||
|
||
using Amazon.Lambda.APIGatewayEvents; | ||
using Amazon.Lambda.TestTool.Models; | ||
using Amazon.Lambda.TestTool.Services; | ||
using Amazon.Lambda.TestTool.Utilities; | ||
using System.Text; | ||
using System.Web; | ||
using static Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest; | ||
|
||
/// <summary> | ||
/// Provides extension methods to translate an <see cref="HttpContext"/> to different types of API Gateway requests. | ||
/// </summary> | ||
public static class HttpContextExtensions | ||
{ | ||
|
||
/// <summary> | ||
/// Translates an <see cref="HttpContext"/> to an <see cref="APIGatewayHttpApiV2ProxyRequest"/>. | ||
/// </summary> | ||
/// <param name="context">The <see cref="HttpContext"/> to be translated.</param> | ||
/// <returns>An <see cref="APIGatewayHttpApiV2ProxyRequest"/> object representing the translated request.</returns> | ||
public static APIGatewayHttpApiV2ProxyRequest ToApiGatewayHttpV2Request( | ||
this HttpContext context, | ||
ApiGatewayRouteConfig apiGatewayRouteConfig) | ||
{ | ||
var request = context.Request; | ||
|
||
var pathParameters = RouteTemplateUtility.ExtractPathParameters(apiGatewayRouteConfig.Path, request.Path); | ||
|
||
var (headers, _) = HttpRequestUtility.ExtractHeaders(request.Headers); | ||
var (queryStringParameters, _) = HttpRequestUtility.ExtractQueryStringParameters(request.Query); | ||
|
||
var httpApiV2ProxyRequest = new APIGatewayHttpApiV2ProxyRequest | ||
{ | ||
RouteKey = $"{request.Method} {apiGatewayRouteConfig.Path}", | ||
RawPath = request.Path, | ||
RawQueryString = request.QueryString.Value, | ||
Cookies = request.Cookies.Select(c => $"{c.Key}={c.Value}").ToArray(), | ||
Headers = headers, | ||
QueryStringParameters = queryStringParameters, | ||
PathParameters = pathParameters ?? new Dictionary<string, string>(), | ||
Body = HttpRequestUtility.ReadRequestBody(request), | ||
IsBase64Encoded = false, | ||
RequestContext = new ProxyRequestContext | ||
{ | ||
Http = new HttpDescription | ||
{ | ||
Method = request.Method, | ||
Path = request.Path, | ||
Protocol = request.Protocol | ||
}, | ||
RouteKey = $"{request.Method} {apiGatewayRouteConfig.Path}" | ||
}, | ||
Version = "2.0" | ||
}; | ||
|
||
if (HttpRequestUtility.IsBinaryContent(request.ContentType)) | ||
{ | ||
httpApiV2ProxyRequest.Body = Convert.ToBase64String(Encoding.UTF8.GetBytes(httpApiV2ProxyRequest.Body)); | ||
httpApiV2ProxyRequest.IsBase64Encoded = true; | ||
} | ||
|
||
return httpApiV2ProxyRequest; | ||
} | ||
|
||
/// <summary> | ||
/// Translates an <see cref="HttpContext"/> to an <see cref="APIGatewayProxyRequest"/>. | ||
/// </summary> | ||
/// <param name="context">The <see cref="HttpContext"/> to be translated.</param> | ||
/// <returns>An <see cref="APIGatewayProxyRequest"/> object representing the translated request.</returns> | ||
public static APIGatewayProxyRequest ToApiGatewayRequest( | ||
this HttpContext context, | ||
ApiGatewayRouteConfig apiGatewayRouteConfig) | ||
{ | ||
var request = context.Request; | ||
|
||
var pathParameters = RouteTemplateUtility.ExtractPathParameters(apiGatewayRouteConfig.Path, request.Path); | ||
|
||
var (headers, multiValueHeaders) = HttpRequestUtility.ExtractHeaders(request.Headers); | ||
var (queryStringParameters, multiValueQueryStringParameters) = HttpRequestUtility.ExtractQueryStringParameters(request.Query); | ||
|
||
var proxyRequest = new APIGatewayProxyRequest | ||
{ | ||
Resource = apiGatewayRouteConfig.Path, | ||
Path = HttpUtility.UrlEncode(request.Path), | ||
HttpMethod = request.Method, | ||
Headers = headers, | ||
MultiValueHeaders = multiValueHeaders, | ||
QueryStringParameters = queryStringParameters, | ||
MultiValueQueryStringParameters = multiValueQueryStringParameters, | ||
PathParameters = pathParameters ?? new Dictionary<string, string>(), | ||
Body = HttpRequestUtility.ReadRequestBody(request), | ||
IsBase64Encoded = false | ||
}; | ||
|
||
if (HttpRequestUtility.IsBinaryContent(request.ContentType)) | ||
{ | ||
proxyRequest.Body = Convert.ToBase64String(Encoding.UTF8.GetBytes(proxyRequest.Body)); | ||
proxyRequest.IsBase64Encoded = true; | ||
} | ||
|
||
return proxyRequest; | ||
} | ||
} |
48 changes: 24 additions & 24 deletions
48
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/ServiceCollectionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,31 @@ | ||
using Amazon.Lambda.TestTool.Services; | ||
using Amazon.Lambda.TestTool.Services.IO; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
|
||
namespace Amazon.Lambda.TestTool.Extensions; | ||
|
||
/// <summary> | ||
/// A class that contains extension methods for the <see cref="IServiceCollection"/> interface. | ||
/// </summary> | ||
public static class ServiceCollectionExtensions | ||
{ | ||
using Amazon.Lambda.TestTool.Services; | ||
using Amazon.Lambda.TestTool.Services.IO; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
|
||
namespace Amazon.Lambda.TestTool.Extensions; | ||
|
||
/// <summary> | ||
/// A class that contains extension methods for the <see cref="IServiceCollection"/> interface. | ||
/// </summary> | ||
public static class ServiceCollectionExtensions | ||
{ | ||
/// <summary> | ||
/// Adds a set of services for the .NET CLI portion of this application. | ||
/// </summary> | ||
public static void AddCustomServices(this IServiceCollection serviceCollection, | ||
ServiceLifetime lifetime = ServiceLifetime.Singleton) | ||
{ | ||
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IToolInteractiveService), typeof(ConsoleInteractiveService), lifetime)); | ||
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDirectoryManager), typeof(DirectoryManager), lifetime)); | ||
/// </summary> | ||
public static void AddCustomServices(this IServiceCollection serviceCollection, | ||
ServiceLifetime lifetime = ServiceLifetime.Singleton) | ||
{ | ||
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IToolInteractiveService), typeof(ConsoleInteractiveService), lifetime)); | ||
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDirectoryManager), typeof(DirectoryManager), lifetime)); | ||
} | ||
|
||
/// <summary> | ||
/// Adds a set of services for the API Gateway emulator portion of this application. | ||
/// </summary> | ||
public static void AddApiGatewayEmulatorServices(this IServiceCollection serviceCollection, | ||
ServiceLifetime lifetime = ServiceLifetime.Singleton) | ||
{ | ||
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IApiGatewayRouteConfigService), typeof(ApiGatewayRouteConfigService), lifetime)); | ||
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IEnvironmentManager), typeof(EnvironmentManager), lifetime)); | ||
} | ||
/// </summary> | ||
public static void AddApiGatewayEmulatorServices(this IServiceCollection serviceCollection, | ||
ServiceLifetime lifetime = ServiceLifetime.Singleton) | ||
{ | ||
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IApiGatewayRouteConfigService), typeof(ApiGatewayRouteConfigService), lifetime)); | ||
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IEnvironmentManager), typeof(EnvironmentManager), lifetime)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/HttpRequestUtility.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
namespace Amazon.Lambda.TestTool | ||
{ | ||
/// <summary> | ||
/// Utility class for handling HTTP requests in the context of API Gateway emulation. | ||
/// </summary> | ||
public static class HttpRequestUtility | ||
{ | ||
/// <summary> | ||
/// Determines whether the specified content type represents binary content. | ||
/// </summary> | ||
/// <param name="contentType">The content type to check.</param> | ||
/// <returns>True if the content type represents binary content; otherwise, false.</returns> | ||
public static bool IsBinaryContent(string? contentType) | ||
{ | ||
if (string.IsNullOrEmpty(contentType)) | ||
return false; | ||
|
||
return contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) || | ||
contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || | ||
contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase) || | ||
contentType.Equals("application/octet-stream", StringComparison.OrdinalIgnoreCase); | ||
} | ||
|
||
/// <summary> | ||
/// Reads the body of the HTTP request as a string. | ||
/// </summary> | ||
/// <param name="request">The HTTP request.</param> | ||
/// <returns>The body of the request as a string.</returns> | ||
public static string ReadRequestBody(HttpRequest request) | ||
{ | ||
using (var reader = new StreamReader(request.Body)) | ||
{ | ||
return reader.ReadToEnd(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Extracts headers from the request, separating them into single-value and multi-value dictionaries. | ||
/// </summary> | ||
/// <param name="headers">The request headers.</param> | ||
/// <returns>A tuple containing single-value and multi-value header dictionaries.</returns> | ||
/// <example> | ||
/// For headers: | ||
/// Accept: text/html | ||
/// Accept: application/xhtml+xml | ||
/// X-Custom-Header: value1 | ||
/// | ||
/// The method will return: | ||
/// singleValueHeaders: { "Accept": "application/xhtml+xml", "X-Custom-Header": "value1" } | ||
/// multiValueHeaders: { "Accept": ["text/html", "application/xhtml+xml"], "X-Custom-Header": ["value1"] } | ||
/// </example> | ||
public static (IDictionary<string, string>, IDictionary<string, IList<string>>) ExtractHeaders(IHeaderDictionary headers) | ||
{ | ||
var singleValueHeaders = new Dictionary<string, string>(); | ||
var multiValueHeaders = new Dictionary<string, IList<string>>(); | ||
|
||
foreach (var header in headers) | ||
{ | ||
singleValueHeaders[header.Key] = header.Value.Last() ?? ""; | ||
multiValueHeaders[header.Key] = [.. header.Value]; | ||
} | ||
|
||
return (singleValueHeaders, multiValueHeaders); | ||
} | ||
|
||
/// <summary> | ||
/// Extracts query string parameters from the request, separating them into single-value and multi-value dictionaries. | ||
/// </summary> | ||
/// <param name="query">The query string collection.</param> | ||
/// <returns>A tuple containing single-value and multi-value query parameter dictionaries.</returns> | ||
/// <example> | ||
/// For query string: ?param1=value1&param2=value2&param2=value3 | ||
/// | ||
/// The method will return: | ||
/// singleValueParams: { "param1": "value1", "param2": "value3" } | ||
/// multiValueParams: { "param1": ["value1"], "param2": ["value2", "value3"] } | ||
/// </example> | ||
public static (IDictionary<string, string>, IDictionary<string, IList<string>>) ExtractQueryStringParameters(IQueryCollection query) | ||
{ | ||
var singleValueParams = new Dictionary<string, string>(); | ||
var multiValueParams = new Dictionary<string, IList<string>>(); | ||
|
||
foreach (var param in query) | ||
{ | ||
singleValueParams[param.Key] = param.Value.Last() ?? ""; | ||
multiValueParams[param.Key] = [.. param.Value]; | ||
} | ||
|
||
return (singleValueParams, multiValueParams); | ||
} | ||
} | ||
} |
Oops, something went wrong.