Skip to content

Commit eb0a529

Browse files
committed
docs: add ALB feature documentation to Annotations README
- Add 'Application Load Balancer (ALB) Example' section with: - Property reference table for ALBApi attribute - Method signature requirements - Prerequisites (existing ALB listener required) - Basic example with @ResourceName template reference - Literal listener ARN example - Advanced example with all optional properties - Generated CloudFormation resources walkthrough - Multi-value headers and condition examples - Minimal ALB infrastructure template snippet - Add ALBApi to Event Attributes reference list - Add Amazon.Lambda.ApplicationLoadBalancerEvents to Project References - Update table of contents
1 parent 6ac3675 commit eb0a529

File tree

1 file changed

+226
-1
lines changed
  • Libraries/src/Amazon.Lambda.Annotations

1 file changed

+226
-1
lines changed

Libraries/src/Amazon.Lambda.Annotations/README.md

Lines changed: 226 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Topics:
1919
- [Amazon API Gateway example](#amazon-api-gateway-example)
2020
- [Amazon S3 example](#amazon-s3-example)
2121
- [SQS Event Example](#sqs-event-example)
22+
- [Application Load Balancer (ALB) Example](#application-load-balancer-alb-example)
2223
- [Custom Lambda Authorizer Example](#custom-lambda-authorizer-example)
2324
- [HTTP API Authorizer](#http-api-authorizer)
2425
- [REST API Authorizer](#rest-api-authorizer)
@@ -852,6 +853,226 @@ The following SQS event source mapping will be generated for the `SQSMessageHand
852853
}
853854
```
854855

856+
## Application Load Balancer (ALB) Example
857+
858+
This example shows how to use the `ALBApi` attribute to configure a Lambda function as a target behind an [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html). Unlike API Gateway event attributes that map to SAM event types, the ALB integration generates standalone CloudFormation resources — a `TargetGroup`, a `ListenerRule`, and a `Lambda::Permission` — to wire the Lambda function to an existing ALB listener.
859+
860+
The `ALBApi` attribute contains the following properties:
861+
862+
| Property | Type | Required | Default | Description |
863+
|---|---|---|---|---|
864+
| `ListenerArn` | `string` | Yes || The ARN of the existing ALB listener, or a `@ResourceName` reference to a listener resource defined in the CloudFormation template. |
865+
| `PathPattern` | `string` | Yes || The path pattern condition for the listener rule (e.g., `"/api/orders/*"`). Supports wildcard characters `*` and `?`. |
866+
| `Priority` | `int` | Yes || The listener rule priority (1–50000). Lower numbers are evaluated first. Must be unique per listener. |
867+
| `MultiValueHeaders` | `bool` | No | `false` | When `true`, enables multi-value headers on the target group. The function should then use `MultiValueHeaders` and `MultiValueQueryStringParameters` on request/response objects. |
868+
| `HostHeader` | `string` | No | `null` | Optional host header condition (e.g., `"api.example.com"`). |
869+
| `HttpMethod` | `string` | No | `null` | Optional HTTP method condition (e.g., `"GET"`, `"POST"`). Leave null to match all methods. |
870+
| `ResourceName` | `string` | No | `"{LambdaResourceName}ALB"` | Custom CloudFormation resource name prefix for the generated resources. Must be alphanumeric. |
871+
872+
The `ALBApi` attribute must be applied to a Lambda method along with the `LambdaFunction` attribute.
873+
874+
The Lambda method must conform to the following rules when tagged with the `ALBApi` attribute:
875+
876+
1. It must have at least 1 argument and can have at most 2 arguments.
877+
- The first argument is required and must be of type `ApplicationLoadBalancerRequest` defined in the [Amazon.Lambda.ApplicationLoadBalancerEvents](https://github.com/aws/aws-lambda-dotnet/tree/master/Libraries/src/Amazon.Lambda.ApplicationLoadBalancerEvents) package.
878+
- The second argument is optional and must be of type `ILambdaContext`.
879+
2. The method return type must be `ApplicationLoadBalancerResponse` or `Task<ApplicationLoadBalancerResponse>`.
880+
881+
### Prerequisites
882+
883+
Your CloudFormation template must include an existing ALB and listener. The `ALBApi` attribute references the listener — it does **not** create the ALB or listener for you. You can define them in the same template or reference one that already exists via its ARN.
884+
885+
### Basic Example
886+
887+
This example creates a simple hello endpoint behind an ALB listener that is defined elsewhere in the template:
888+
889+
```csharp
890+
using Amazon.Lambda.Annotations;
891+
using Amazon.Lambda.Annotations.ALB;
892+
using Amazon.Lambda.ApplicationLoadBalancerEvents;
893+
using Amazon.Lambda.Core;
894+
using System.Collections.Generic;
895+
896+
public class ALBFunctions
897+
{
898+
[LambdaFunction(ResourceName = "ALBHello", MemorySize = 256, Timeout = 15)]
899+
[ALBApi("@ALBTestListener", "/hello", 1)]
900+
public ApplicationLoadBalancerResponse Hello(ApplicationLoadBalancerRequest request, ILambdaContext context)
901+
{
902+
context.Logger.LogInformation($"Hello endpoint hit. Path: {request.Path}");
903+
904+
return new ApplicationLoadBalancerResponse
905+
{
906+
StatusCode = 200,
907+
StatusDescription = "200 OK",
908+
IsBase64Encoded = false,
909+
Headers = new Dictionary<string, string>
910+
{
911+
{ "Content-Type", "application/json" }
912+
},
913+
Body = $"{{\"message\": \"Hello from ALB Lambda!\", \"path\": \"{request.Path}\"}}"
914+
};
915+
}
916+
}
917+
```
918+
919+
In the example above, `@ALBTestListener` references a listener resource called `ALBTestListener` defined in the same CloudFormation template. The `@` prefix tells the source generator to use a `Ref` intrinsic function instead of a literal ARN string.
920+
921+
### Using a Literal Listener ARN
922+
923+
If you want to reference an ALB listener in a different stack or one that was created outside of CloudFormation, use the full ARN:
924+
925+
```csharp
926+
[LambdaFunction(ResourceName = "ALBHandler")]
927+
[ALBApi("arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/abc123/def456", "/api/*", 10)]
928+
public ApplicationLoadBalancerResponse HandleRequest(ApplicationLoadBalancerRequest request, ILambdaContext context)
929+
{
930+
return new ApplicationLoadBalancerResponse
931+
{
932+
StatusCode = 200,
933+
Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } },
934+
Body = "{\"status\": \"ok\"}"
935+
};
936+
}
937+
```
938+
939+
### Advanced Example with All Options
940+
941+
This example shows all optional properties including host header filtering, HTTP method filtering, multi-value headers, and a custom resource name:
942+
943+
```csharp
944+
[LambdaFunction(ResourceName = "ALBOrders")]
945+
[ALBApi("@MyListener", "/api/orders/*", 5,
946+
MultiValueHeaders = true,
947+
HostHeader = "api.example.com",
948+
HttpMethod = "POST",
949+
ResourceName = "OrdersALB")]
950+
public ApplicationLoadBalancerResponse CreateOrder(ApplicationLoadBalancerRequest request, ILambdaContext context)
951+
{
952+
// When MultiValueHeaders is true, use MultiValueHeaders and MultiValueQueryStringParameters
953+
var contentTypes = request.MultiValueHeaders?["content-type"];
954+
955+
return new ApplicationLoadBalancerResponse
956+
{
957+
StatusCode = 201,
958+
StatusDescription = "201 Created",
959+
MultiValueHeaders = new Dictionary<string, IList<string>>
960+
{
961+
{ "Content-Type", new List<string> { "application/json" } },
962+
{ "X-Custom-Header", new List<string> { "value1", "value2" } }
963+
},
964+
Body = "{\"orderId\": \"12345\"}"
965+
};
966+
}
967+
```
968+
969+
### Generated CloudFormation Resources
970+
971+
For each `ALBApi` attribute, the source generator creates three CloudFormation resources. Here is an example of the generated template for the basic hello endpoint:
972+
973+
```json
974+
"ALBHello": {
975+
"Type": "AWS::Serverless::Function",
976+
"Metadata": {
977+
"Tool": "Amazon.Lambda.Annotations"
978+
},
979+
"Properties": {
980+
"Runtime": "dotnet8",
981+
"CodeUri": ".",
982+
"MemorySize": 256,
983+
"Timeout": 15,
984+
"Policies": ["AWSLambdaBasicExecutionRole"],
985+
"PackageType": "Zip",
986+
"Handler": "MyProject::MyNamespace.ALBFunctions_Hello_Generated::Hello"
987+
}
988+
},
989+
"ALBHelloALBPermission": {
990+
"Type": "AWS::Lambda::Permission",
991+
"Metadata": { "Tool": "Amazon.Lambda.Annotations" },
992+
"Properties": {
993+
"FunctionName": { "Fn::GetAtt": ["ALBHello", "Arn"] },
994+
"Action": "lambda:InvokeFunction",
995+
"Principal": "elasticloadbalancing.amazonaws.com"
996+
}
997+
},
998+
"ALBHelloALBTargetGroup": {
999+
"Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
1000+
"Metadata": { "Tool": "Amazon.Lambda.Annotations" },
1001+
"DependsOn": "ALBHelloALBPermission",
1002+
"Properties": {
1003+
"TargetType": "lambda",
1004+
"Targets": [
1005+
{ "Id": { "Fn::GetAtt": ["ALBHello", "Arn"] } }
1006+
]
1007+
}
1008+
},
1009+
"ALBHelloALBListenerRule": {
1010+
"Type": "AWS::ElasticLoadBalancingV2::ListenerRule",
1011+
"Metadata": { "Tool": "Amazon.Lambda.Annotations" },
1012+
"Properties": {
1013+
"ListenerArn": { "Ref": "ALBTestListener" },
1014+
"Priority": 1,
1015+
"Conditions": [
1016+
{ "Field": "path-pattern", "Values": ["/hello"] }
1017+
],
1018+
"Actions": [
1019+
{ "Type": "forward", "TargetGroupArn": { "Ref": "ALBHelloALBTargetGroup" } }
1020+
]
1021+
}
1022+
}
1023+
```
1024+
1025+
When `MultiValueHeaders` is set to `true`, the target group will include a `TargetGroupAttributes` section:
1026+
1027+
```json
1028+
"TargetGroupAttributes": [
1029+
{ "Key": "lambda.multi_value_headers.enabled", "Value": "true" }
1030+
]
1031+
```
1032+
1033+
When `HostHeader` or `HttpMethod` are specified, additional conditions are added to the listener rule:
1034+
1035+
```json
1036+
"Conditions": [
1037+
{ "Field": "path-pattern", "Values": ["/api/orders/*"] },
1038+
{ "Field": "host-header", "Values": ["api.example.com"] },
1039+
{ "Field": "http-request-method", "Values": ["POST"] }
1040+
]
1041+
```
1042+
1043+
### Setting Up the ALB in the Template
1044+
1045+
The `ALBApi` attribute requires an existing ALB listener. Here is a minimal example of the infrastructure resources you would add to your `serverless.template`:
1046+
1047+
```json
1048+
{
1049+
"MyVPC": { "Type": "AWS::EC2::VPC", "Properties": { "CidrBlock": "10.0.0.0/16" } },
1050+
"MySubnet1": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "MyVPC" }, "CidrBlock": "10.0.1.0/24" } },
1051+
"MySubnet2": { "Type": "AWS::EC2::Subnet", "Properties": { "VpcId": { "Ref": "MyVPC" }, "CidrBlock": "10.0.2.0/24" } },
1052+
"MySecurityGroup": { "Type": "AWS::EC2::SecurityGroup", "Properties": { "GroupDescription": "ALB SG", "VpcId": { "Ref": "MyVPC" } } },
1053+
"MyALB": {
1054+
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
1055+
"Properties": {
1056+
"Type": "application",
1057+
"Scheme": "internet-facing",
1058+
"Subnets": [{ "Ref": "MySubnet1" }, { "Ref": "MySubnet2" }],
1059+
"SecurityGroups": [{ "Ref": "MySecurityGroup" }]
1060+
}
1061+
},
1062+
"MyListener": {
1063+
"Type": "AWS::ElasticLoadBalancingV2::Listener",
1064+
"Properties": {
1065+
"LoadBalancerArn": { "Ref": "MyALB" },
1066+
"Port": 80,
1067+
"Protocol": "HTTP",
1068+
"DefaultAction": [{ "Type": "fixed-response", "FixedResponseConfig": { "StatusCode": "404" } }]
1069+
}
1070+
}
1071+
}
1072+
```
1073+
1074+
Then your Lambda function references `@MyListener` in the `ALBApi` attribute.
1075+
8551076
## Custom Lambda Authorizer Example
8561077

8571078
Lambda Annotations supports defining custom Lambda authorizers using attributes. Custom authorizers let you control access to your API Gateway endpoints by running a Lambda function that validates tokens or request parameters before the target function is invoked. The source generator automatically wires up the authorizer resources and references in the CloudFormation template.
@@ -1198,7 +1419,9 @@ parameter to the `LambdaFunction` must be the event object and the event source
11981419
* RestApiAuthorizer
11991420
* Marks a Lambda function as a REST API (API Gateway V1) custom authorizer. The authorizer name is automatically derived from the method name. Other functions reference it via `RestApi.Authorizer` using `nameof()`. Use the `Type` property to choose between `Token` and `Request` authorizer types.
12001421
* SQSEvent
1201-
* Sets up event source mapping between the Lambda function and SQS queues. The SQS queue ARN is required to be set on the attribute. If users want to pass a reference to an existing SQS queue resource defined in their CloudFormation template, they can pass the SQS queue resource name prefixed with the '@' symbol.
1422+
* Sets up event source mapping between the Lambda function and SQS queues. The SQS queue ARN is required to be set on the attribute. If users want to pass a reference to an existing SQS queue resource defined in their CloudFormation template, they can pass the SQS queue resource name prefixed with the '@' symbol.
1423+
* ALBApi
1424+
* Configures the Lambda function to be called from an Application Load Balancer. The listener ARN (or `@ResourceName` template reference), path pattern, and priority are required. The source generator creates standalone CloudFormation resources (TargetGroup, ListenerRule, Lambda Permission) rather than SAM event types.
12021425

12031426
### Parameter Attributes
12041427

@@ -1277,3 +1500,5 @@ The content type is determined using the following rules.
12771500
## Project References
12781501

12791502
If API Gateway event attributes, such as `RestAPI` or `HttpAPI`, are being used then a package reference to `Amazon.Lambda.APIGatewayEvents` must be added to the project, otherwise the project will not compile. We do not include it by default in order to keep the `Amazon.Lambda.Annotations` library lightweight.
1503+
1504+
Similarly, if the `ALBApi` attribute is being used then a package reference to `Amazon.Lambda.ApplicationLoadBalancerEvents` must be added to the project. This provides the `ApplicationLoadBalancerRequest` and `ApplicationLoadBalancerResponse` types used by ALB Lambda functions.

0 commit comments

Comments
 (0)