Is it a good idea to call one AWS Lambda from another with an APIGatewayProxyRequest?

Viewed 1073

This question is inspired by this other: Is it a good idea to invoke lambda from other lambda in AWS lambda service?

I am using netcoreapp3.1 and C# to develop lambda.

I have one lambda A that accepts HTTP requests from API Gateway. I am using the dotnet template serverless.AspNetCoreWebAPI as per https://aws.amazon.com/blogs/developer/running-serverless-asp-net-core-web-apis-with-amazon-lambda where my lambda function reacts to invokations as if it was a web api.

I have another lambda B that also gets invoked by API Gateway, only this time it gets websocket events (CONNECT, DISCONNECT, MESSAGE).

I am thinking of having lambda B invoking lambda A synchronously.

The flow would be like this: API Gateway > Lambda B > Lambda A

I have here a dilemma:

  • Is it a good idea to invoke directly Lambda A from Lambda B?
  • Or is it better to have Lambda B send an http request to API Gateway and leave the gateway do its thing invoking Lambda A?
  • Or should I forget about exposing Lambda A functionality as "http" and have a single entry point in Lambda A that accepts ANY payload so that Lambda B can invoke it with a custom message?

Cost and performance wise I guess there is no much difference, but invoking Lambda A from Lambda B seems "easier" as the only thing I really need is to know the name of the function.

Here comes my attempt to do that, I use the

public async Task<APIGatewayProxyResponse> FunctionHandler(
    APIGatewayProxyRequest apiGatewayProxyRequest, 
    ILambdaContext context)
{
    context.Logger.LogLine("Get Request\n");
    var isWebSocketRequest = apiGatewayProxyRequest.HttpMethod is null;
    if (!isWebSocketRequest)
    {
        throw new ArgumentException(
            $"Received Http method {apiGatewayProxyRequest.HttpMethod}, not a valid websocket event");
    }

    var result = await ProcessWebsocketEvent(apiGatewayProxyRequest, context.Logger);
    return result;
}

private async Task<APIGatewayProxyResponse> ProcessWebsocketEvent(APIGatewayProxyRequest apiGatewayProxyRequest, ILambdaLogger logger)
{
    var eventType = apiGatewayProxyRequest.RequestContext.EventType;
    string body = eventType;
    if ("CONNECT" == eventType)
    {
        body += $", connection={apiGatewayProxyRequest.RequestContext.ConnectionId}";
    }
    else if ("DISCONNECT" == eventType)
    {
        body += $", connection={apiGatewayProxyRequest.RequestContext.ConnectionId}";
    }
    else if ("MESSAGE" == eventType)
    {
        body += $", body={apiGatewayProxyRequest.Body}";
    }

    logger.LogLine("Creating amazon lambda client..");
    using var lambdaClient = new AmazonLambdaClient(RegionEndpoint.EUWest3);
    var payload =
        new
        {
            Foo = "bar",
            Bar = "foo"
        };
    var json = JsonSerializer.Serialize(payload);
    logger.LogLine($"With payload {payload}");
    var request =
        new InvokeRequest
        {
            FunctionName = "lambda-dotnet-webapi-function",
            Payload = json
        };
    
    var responseInvoke = await lambdaClient.InvokeAsync(request);
    using var stream = new StreamReader(responseInvoke.Payload);
    var responsePayload = await stream.ReadToEndAsync();

    var response = new APIGatewayProxyResponse
    {
        StatusCode = (int) HttpStatusCode.OK,
        Body = responsePayload,
        Headers = new Dictionary<string, string> {{"Content-Type", "text/plain"}}
    };
    return response;
}

First thing I noticed. If I have a 3 seconds timeout, it times out. I have to increase it. If I increase it to 10 seconds I am able to get an invocation as the invoke request's payload is not "correct"

{"statusCode":200,"headers":{"Content-Type":"text/plain"},"body":"{\n  \"errorType\": \"NullReferenceException\",\n  \"errorMessage\": \"Object reference not set to an instance of an object.\",\n  \"stackTrace\": [\n    \"at Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction.MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext)\",\n    \"at Amazon.Lambda.AspNetCoreServer.AbstractAspNetCoreFunction`2.FunctionHandlerAsync(TREQUEST request, ILambdaContext lambdaContext)\",\n    \"at lambda_method(Closure , Stream , Stream , LambdaContextInternal )\"\n  ]\n}\n","isBase64Encoded":false}

The Lambda A runs for 800ms The Lambda B runs for 9000ms (9 seconds, a lot..)

This brings me to another question. If this is an acceptable approach, how can I send the request as if Lambda B was a "proxy"?

I am thinking of invoking Lambda A in an http-ish way, mainly because I really like the library https://github.com/aws/aws-lambda-dotnet/tree/master/Libraries/src/Amazon.Lambda.AspNetCoreServer which allows my Lambda to be implemented as a standard aspNet web api, read appsettings, dependency injection, test with test server, etc.

Looking at Lambda A logs I can see the following warning

[Warning] Amazon.Lambda.AspNetCoreServer.AbstractAspNetCoreFunction: Request does not contain domain name information but is derived from APIGatewayProxyFunction. 

and the exception that I am getting as the response in Lambda B:

Object reference not set to an instance of an object.: NullReferenceException
   at Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction.MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext)
   at Amazon.Lambda.AspNetCoreServer.AbstractAspNetCoreFunction`2.FunctionHandlerAsync(TREQUEST request, ILambdaContext lambdaContext)
   at lambda_method(Closure , Stream , Stream , LambdaContextInternal )
1 Answers

The problem is that you are connecting to from lambda B and lambda A needs to warm up this coulf take a few seconds (at least 3) so if your timeout on lambda B is 3 seconds you receives a timeout

A solution for this is mantain in warm up your lambda A, maybe with a ping operation, but this implicates that you will have a lambda up most of time

Othe thing

could you add

InvocationType = InvocationType.RequestResponse,

to InvokeRequest

Related