Lambda Function URLs - triggering .NET 6 Lambda functions with a HTTPS Request
Want to learn more about AWS Lambda and .NET? Check out my A Cloud Guru course on ASP.NET Web API and Lambda.
Download full source code.
I love using .NET Lambda functions to handle incoming HTTP requests, but until now an API Gateway or an Application Load Balancer had to be configured and connected to the function. Not hard to do, especially if you use the deployment tools provided with the AWS Extensions for the .NET CLI. I have an example here that uses the API Gateway. As part of the process you have to create an S3 bucket, then the tooling creates a CloudFormation stack, deploys the Lambda, creates the API Gateway, roles, permissions, etc, and hooks everything up.
Last week, a new feature was released, Lambda Function URLs. You no longer have to create the S3 bucket, no CloudFormation stack, and no API Gateway. Read Alex Casalboni’s blog for more details on how it works and some use cases for this feature.
The Function URL is configured directly on the Lambda function, either from the AWS Console, the AWS CLI, PowerShell tools, or an IaC tool. In this post, you will use the AWS CLI.
Getting the tools
Install the latest tooling, this lets you deploy and run Lambda functions.
dotnet tool install -g Amazon.Lambda.Tools
Install the latest templates to get .NET 6 support.
dotnet new --install Amazon.Lambda.Templates
Get the latest version of the AWS CLI, from here.
Create a simple application for the Lambda function
From the command line, run -
dotnet new lambda.EmptyFunction --name FunctionUrlExample
Go to the directory .\FunctionUrlExample\src\FunctionUrlExample\
.
The FunctionHandler
method is very simple -
public string FunctionHandler(string input, ILambdaContext context)
{
return input.ToUpper();
}
Altering the Lambda function to accept the Function URL request
When the Lambda function is triggered from the Function URL the incoming request will contain all the HTTP info you would expect, query-string, body, headers, method, etc, this will be passed to Lambda function as JSON. To accept this, you will change the first parameter in the FunctionHandler
method from a string
to an APIGatewayHttpApiV2ProxyRequest
.
But first, add the Amazon.Lambda.APIGatewayEvents
package to the project -
dotnet add package Amazon.Lambda.APIGatewayEvents
Add a using statement to the Function.cs
file -
using Amazon.Lambda.APIGatewayEvents;
Change the FunctionHandler
signature to -
public string FunctionHandler(APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context)
With this change, the incoming request will be deserialized into the request
object.
If you are responding to a GET
or another verb that doesn’t use a body (I know, GET
technically can have a body, but it usually doesn’t), you can extract the query string, path, etc from the request
object. For example, try -
request.QueryStringParameters
request.PathParameters
request.RequestContext.Http.Method
But if you want to use the body of the request, another deserialization must be performed.
Deserializing the body of the request
The APIGatewayHttpApiV2ProxyRequest.Body
contains the body of any request that supports a body (PUT, POST, PATCH, etc). But this is a string and has to be explicitly deserialized into a type you define.
In this example, you will deserialize a Person
from the body of the request.
Create a new class called Person
.
public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public int Age { get; init; }
}
Back in Function.cs
, add two using statements -
using System.Text.Json;
using System.Text.Json.Serialization;
And change the FunctionHandler
method to this -
public string FunctionHandler(APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context)
{
var serializationOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
NumberHandling= JsonNumberHandling.AllowReadingFromString
};
Person person = JsonSerializer.Deserialize<Person>(request.Body, serializationOptions);
return $"Hello {person.FirstName} {person.LastName}, you are {person.Age} years old.";
}
Setting serializationOptions
is not necessary but makes it easier for your function to handle capitalization issues and the presence of numbers as strings in the request.
Deploy the function
Use the following to build the code and deploy the Lambda function -
dotnet lambda deploy-function FunctionUrlExample
You will then be asked - “Enter name of the new IAM Role:”, put in “FunctionUrlExampleRole”.
Then you will be asked to - “Select IAM Policy to attach to the new role and grant permissions”, select “AWSLambdaBasicExecutionRole”, for me it is number 6 on the list.
Wait as the function and permissions are created.
Configuring the function for HTTP requests
Right now, the Lambda function is deployed and can be seen in the AWS Console, but it doesn’t have a Function URL.
You can add one from the AWS Console or the AWS CLI (or other tools). But I want to use the AWS CLI, a minimum version of “2.5.4” is required (see above on tooling).
From the command line, run -
aws lambda create-function-url-config --function-name FunctionUrlExample --auth-type NONE
You will see -
{
"FunctionUrl": "https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.lambda-url.us-east-1.on.aws/",
"FunctionArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxx:function:FunctionUrlExample",
"AuthType": "NONE",
"CreationTime": "2022-04-09T00:43:45.699805Z"
}
At this point, a URL is attached to the Lambda function, requiring no authentication, but you’re not quite finished yet.
Add a resource-based policy to allow the Function URL to invoke the Lambda function
One more thing to do - add a resource-based policy allowing the Lambda function to be called via the URL. See here for more.
aws lambda add-permission --function-name FunctionUrlExample --statement-id AuthNone --action lambda:InvokeFunctionUrl --principal * --function-url-auth-type NONE
You will get a response like -
{
"Statement": "{"Sid":"AuthNone","Effect":"Allow","Principal":"*","Action":"lambda:InvokeFunctionUrl","Resource":"arn:aws:lambda:us-east-1:xxxxxxxxx:function:FunctionUrlExample","Condition":{"StringEquals":{"lambda:FunctionUrlAuthType":"NONE"}}}"
}
Now the Lambda function can be accessed from the URL with no authentication needed.
Testing it out
In the attached zip file, there is a file TestFunction.rest
, which works with the Visual Studio Code extension Rest Client.
POST https://xxxxxxxxxxxxxxx.lambda-url.us-east-1.on.aws/ HTTP/1.1
content-type: application/json
{
"firstname": "Alan",
"lastname": "Adams",
"age": "25"
}
You can of course use Curl, Fiddler, Postman, etc.
There it is, a publicly accessible HTTPS endpoint, backed by C# application, that can scale as needed, and be deployed in seconds. Not bad!
Download full source code.