Skip to content

Commit

Permalink
feat: Add support for AWS Lambda APIGatewayHttpApiV2ProxyRequest (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
tippmar-nr authored May 8, 2024
1 parent 402ba16 commit 3f06bf6
Show file tree
Hide file tree
Showing 11 changed files with 829 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum AwsLambdaEventType
{
Unknown,
APIGatewayProxyRequest,
APIGatewayHttpApiV2ProxyRequest,
ApplicationLoadBalancerRequest,
CloudWatchScheduledEvent,
KinesisStreamingEvent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public static AwsLambdaEventType ToEventType(this string typeFullName)
return typeFullName switch
{
"Amazon.Lambda.APIGatewayEvents.APIGatewayProxyRequest" => AwsLambdaEventType.APIGatewayProxyRequest,
"Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest" => AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest,
"Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest" => AwsLambdaEventType.ApplicationLoadBalancerRequest,
"Amazon.Lambda.CloudWatchEvents.ScheduledEvents.ScheduledEvent" => AwsLambdaEventType.CloudWatchScheduledEvent,
"Amazon.Lambda.KinesisEvents.KinesisEvent" => AwsLambdaEventType.KinesisStreamingEvent,
Expand All @@ -32,6 +33,7 @@ public static string ToEventTypeString(this AwsLambdaEventType eventType)
return eventType switch
{
AwsLambdaEventType.APIGatewayProxyRequest => "apiGateway",
AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest => "apiGateway",
AwsLambdaEventType.ApplicationLoadBalancerRequest => "alb",
AwsLambdaEventType.CloudWatchScheduledEvent => "cloudWatch_scheduled",
AwsLambdaEventType.KinesisStreamingEvent => "kinesis",
Expand All @@ -48,6 +50,6 @@ public static string ToEventTypeString(this AwsLambdaEventType eventType)

public static bool IsWebEvent(this AwsLambdaEventType eventType)
{
return eventType == AwsLambdaEventType.APIGatewayProxyRequest || eventType == AwsLambdaEventType.ApplicationLoadBalancerRequest;
return eventType == AwsLambdaEventType.APIGatewayProxyRequest || eventType == AwsLambdaEventType.ApplicationLoadBalancerRequest || eventType == AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static void AddEventTypeAttributes(IAgent agent, ITransaction transaction
{
case AwsLambdaEventType.APIGatewayProxyRequest:
dynamic apiReqEvent = inputObject; // Amazon.Lambda.APIGatewayEvents.APIGatewayProxyRequest
SetWebRequestProperties(agent, transaction, apiReqEvent);
SetWebRequestProperties(agent, transaction, apiReqEvent, eventType);

if (apiReqEvent.RequestContext != null)
{
Expand All @@ -34,14 +34,29 @@ public static void AddEventTypeAttributes(IAgent agent, ITransaction transaction
transaction.AddEventSourceAttribute("resourceId", (string)requestContext.ResourceId);
transaction.AddEventSourceAttribute("resourcePath", (string)requestContext.ResourcePath);
transaction.AddEventSourceAttribute("stage", (string)requestContext.Stage);
}
break;

case AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest:
dynamic apiReqv2Event = inputObject; // Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest
SetWebRequestProperties(agent, transaction, apiReqv2Event, eventType);

if (apiReqv2Event.RequestContext != null)
{
dynamic requestContext = apiReqv2Event.RequestContext;
// arn is not available
transaction.AddEventSourceAttribute("accountId", (string)requestContext.AccountId);
transaction.AddEventSourceAttribute("apiId", (string)requestContext.ApiId);
// resourceId is not available for v2
// resourcePath is not available for v2
transaction.AddEventSourceAttribute("stage", (string)requestContext.Stage);
}
break;

case AwsLambdaEventType.ApplicationLoadBalancerRequest:
dynamic albReqEvent = inputObject; //Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest

SetWebRequestProperties(agent, transaction, albReqEvent);
SetWebRequestProperties(agent, transaction, albReqEvent, eventType);

transaction.AddEventSourceAttribute("arn", (string)albReqEvent.RequestContext.Elb.TargetGroupArn);
break;
Expand Down Expand Up @@ -231,26 +246,30 @@ private static void TryParseSNSDistributedTraceHeaders(dynamic snsEvent, ITransa
transaction.AcceptDistributedTraceHeaders(snsHeaders, GetHeaderValue, TransportType.Other);
}

private static void SetWebRequestProperties(IAgent agent, ITransaction transaction, dynamic webReqEvent)
private static void SetWebRequestProperties(IAgent agent, ITransaction transaction, dynamic webReqEvent, AwsLambdaEventType eventType)
{
//HTTP headers
IDictionary<string, string> headers = webReqEvent.Headers;
Func<IDictionary<string, string>, string, string> headersGetter = (h, k) => h[k];

IDictionary<string, IList<string>> multiValueHeaders = webReqEvent.MultiValueHeaders;
Func<IDictionary<string, IList<string>>, string, string> multiValueHeadersGetter = (h, k) => string.Join(",", h[k]);

if (multiValueHeaders != null)
if (eventType != AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest) // v2 doesn't have MultiValueHeaders
{
transaction.SetRequestHeaders(multiValueHeaders, agent.Configuration.AllowAllRequestHeaders ? multiValueHeaders.Keys : Statics.DefaultCaptureHeaders, multiValueHeadersGetter);
IDictionary<string, IList<string>> multiValueHeaders = webReqEvent.MultiValueHeaders;
Func<IDictionary<string, IList<string>>, string, string> multiValueHeadersGetter = (h, k) => string.Join(",", h[k]);

// DT transport comes from the X-Forwarded-Proto header, if present
var forwardedProto = GetMultiHeaderValue(multiValueHeaders, xForwardedProtoHeader).FirstOrDefault();
var dtTransport = GetDistributedTransportType(forwardedProto);
if (multiValueHeaders != null)
{
transaction.SetRequestHeaders(multiValueHeaders, agent.Configuration.AllowAllRequestHeaders ? multiValueHeaders.Keys : Statics.DefaultCaptureHeaders, multiValueHeadersGetter);

// DT transport comes from the X-Forwarded-Proto header, if present
var forwardedProto = GetMultiHeaderValue(multiValueHeaders, xForwardedProtoHeader).FirstOrDefault();
var dtTransport = GetDistributedTransportType(forwardedProto);

transaction.AcceptDistributedTraceHeaders(multiValueHeaders, GetMultiHeaderValue, dtTransport);
transaction.AcceptDistributedTraceHeaders(multiValueHeaders, GetMultiHeaderValue, dtTransport);
}
}
else if (headers != null)

if (headers != null)
{
transaction.SetRequestHeaders(headers, agent.Configuration.AllowAllRequestHeaders ? webReqEvent.Headers?.Keys : Statics.DefaultCaptureHeaders, headersGetter);

Expand All @@ -261,8 +280,18 @@ private static void SetWebRequestProperties(IAgent agent, ITransaction transacti
transaction.AcceptDistributedTraceHeaders(headers, GetHeaderValue, dtTransport);
}

transaction.SetRequestMethod(webReqEvent.HttpMethod);
transaction.SetUri(webReqEvent.Path);
if (eventType == AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest) // v2 buries method and path
{
var reqContext = webReqEvent.RequestContext;
transaction.SetRequestMethod(reqContext.Http.Method);
transaction.SetUri(reqContext.Http.Path);
}
else
{
transaction.SetRequestMethod(webReqEvent.HttpMethod);
transaction.SetUri(webReqEvent.Path);
}

transaction.SetRequestParameters(webReqEvent.QueryStringParameters);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ public object GetInputObject(InstrumentedMethodCall instrumentedMethodCall)
}
return null;
}

public bool IsWebRequest => EventType is AwsLambdaEventType.APIGatewayProxyRequest or AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest or AwsLambdaEventType.ApplicationLoadBalancerRequest;
}

private List<string> _webResponseHeaders = ["content-type", "content-length"];
Expand Down Expand Up @@ -249,7 +251,7 @@ void InvokeTryProcessResponse(Task responseTask)
}

// capture response data for specific request / response types
if (_functionDetails.EventType is AwsLambdaEventType.APIGatewayProxyRequest or AwsLambdaEventType.ApplicationLoadBalancerRequest)
if (_functionDetails.IsWebRequest)
{
var responseGetter = _getRequestResponseFromGeneric ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(responseTask.GetType(), "Result");
var response = responseGetter(responseTask);
Expand All @@ -268,7 +270,7 @@ void InvokeTryProcessResponse(Task responseTask)
return Delegates.GetDelegateFor<object>(
onSuccess: response =>
{
if (_functionDetails.EventType is AwsLambdaEventType.APIGatewayProxyRequest or AwsLambdaEventType.ApplicationLoadBalancerRequest)
if (_functionDetails.IsWebRequest)
CaptureResponseData(transaction, response, agent);

segment.End();
Expand All @@ -290,6 +292,8 @@ private void CaptureResponseData(ITransaction transaction, object response, IAge
// check response type based on request type to be sure it has the properties we're looking for
var responseType = response.GetType().FullName;
if ((_functionDetails.EventType == AwsLambdaEventType.APIGatewayProxyRequest && responseType != "Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse")
||
(_functionDetails.EventType == AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest && responseType != "Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse")
||
(_functionDetails.EventType == AwsLambdaEventType.ApplicationLoadBalancerRequest && responseType != "Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse"))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ private static void Main(string[] args)
return HandlerWrapper.GetHandlerWrapper<APIGatewayProxyRequest, Stream>(ApiGatewayProxyRequestHandlerReturnsStream, serializer);
case nameof (ApiGatewayProxyRequestHandlerReturnsStreamAsync):
return HandlerWrapper.GetHandlerWrapper<APIGatewayProxyRequest, Stream>(ApiGatewayProxyRequestHandlerReturnsStreamAsync, serializer);
case nameof(ApiGatewayHttpApiV2ProxyRequestHandler):
return HandlerWrapper.GetHandlerWrapper<APIGatewayHttpApiV2ProxyRequest, APIGatewayHttpApiV2ProxyResponse>(ApiGatewayHttpApiV2ProxyRequestHandler, serializer);
case nameof(ApiGatewayHttpApiV2ProxyRequestHandlerAsync):
return HandlerWrapper.GetHandlerWrapper<APIGatewayHttpApiV2ProxyRequest, APIGatewayHttpApiV2ProxyResponse>(ApiGatewayHttpApiV2ProxyRequestHandlerAsync, serializer);
case nameof(ApplicationLoadBalancerRequestHandler):
return HandlerWrapper.GetHandlerWrapper<ApplicationLoadBalancerRequest, ApplicationLoadBalancerResponse>(ApplicationLoadBalancerRequestHandler, serializer);
case nameof(ApplicationLoadBalancerRequestHandlerAsync):
Expand Down Expand Up @@ -263,6 +267,14 @@ public static APIGatewayProxyResponse ApiGatewayProxyRequestHandler(APIGatewayPr
return new APIGatewayProxyResponse() { Body = apiGatewayProxyRequest.Body, StatusCode = 200, Headers = new Dictionary<string, string> { { "Content-Type", "application/json" }, { "Content-Length", "12345" } } };
}

public static async Task<APIGatewayProxyResponse> ApiGatewayProxyRequestHandlerAsync(APIGatewayProxyRequest apiGatewayProxyRequest, ILambdaContext __)
{
Console.WriteLine("Executing lambda {0}", nameof(ApiGatewayProxyRequestHandlerAsync));
await Task.Delay(100);

return new APIGatewayProxyResponse() { Body = apiGatewayProxyRequest.Body, StatusCode = 200, Headers = new Dictionary<string, string> { { "Content-Type", "application/json" }, { "Content-Length", "12345" } } };
}

public static Stream ApiGatewayProxyRequestHandlerReturnsStream(APIGatewayProxyRequest apiGatewayProxyRequest, ILambdaContext __)
{
Console.WriteLine("Executing lambda {0}", nameof(ApiGatewayProxyRequestHandlerReturnsStream));
Expand Down Expand Up @@ -290,12 +302,19 @@ public static async Task<Stream> ApiGatewayProxyRequestHandlerReturnsStreamAsync
return stream;
}

public static async Task<APIGatewayProxyResponse> ApiGatewayProxyRequestHandlerAsync(APIGatewayProxyRequest apiGatewayProxyRequest, ILambdaContext __)
public static APIGatewayHttpApiV2ProxyResponse ApiGatewayHttpApiV2ProxyRequestHandler(APIGatewayHttpApiV2ProxyRequest apiGatewayProxyRequest, ILambdaContext __)
{
Console.WriteLine("Executing lambda {0}", nameof(ApiGatewayProxyRequestHandlerAsync));
Console.WriteLine("Executing lambda {0}", nameof(ApiGatewayHttpApiV2ProxyRequestHandler));

return new APIGatewayHttpApiV2ProxyResponse() { Body = apiGatewayProxyRequest.Body, StatusCode = 200, Headers = new Dictionary<string, string> { { "Content-Type", "application/json" }, { "Content-Length", "12345" } } };
}

public static async Task<APIGatewayHttpApiV2ProxyResponse> ApiGatewayHttpApiV2ProxyRequestHandlerAsync(APIGatewayHttpApiV2ProxyRequest apiGatewayProxyRequest, ILambdaContext __)
{
Console.WriteLine("Executing lambda {0}", nameof(ApiGatewayHttpApiV2ProxyRequestHandlerAsync));
await Task.Delay(100);

return new APIGatewayProxyResponse() { Body = apiGatewayProxyRequest.Body, StatusCode = 200, Headers = new Dictionary<string, string> { { "Content-Type", "application/json" }, { "Content-Length", "12345" } } };
return new APIGatewayHttpApiV2ProxyResponse() { Body = apiGatewayProxyRequest.Body, StatusCode = 200, Headers = new Dictionary<string, string> { { "Content-Type", "application/json" }, { "Content-Length", "12345" } } };
}

public static ApplicationLoadBalancerResponse ApplicationLoadBalancerRequestHandler(ApplicationLoadBalancerRequest applicationLoadBalancerRequest, ILambdaContext __)
Expand Down
Loading

0 comments on commit 3f06bf6

Please sign in to comment.