Skip to content

Commit 75d2ff9

Browse files
docs(middleware-factory): snippets split, improved, and lint (#1451)
Co-authored-by: heitorlessa <[email protected]>
1 parent 2cf223d commit 75d2ff9

17 files changed

+746
-51
lines changed
50.8 KB
Loading
78.5 KB
Loading

docs/utilities/middleware_factory.md

+106-51
Original file line numberDiff line numberDiff line change
@@ -3,89 +3,144 @@ title: Middleware factory
33
description: Utility
44
---
55

6+
<!-- markdownlint-disable MD043 -->
7+
68
Middleware factory provides a decorator factory to create your own middleware to run logic before, and after each Lambda invocation synchronously.
79

810
## Key features
911

1012
* Run logic before, after, and handle exceptions
11-
* Trace each middleware when requested
13+
* Built-in tracing opt-in capability
14+
15+
## Getting started
16+
17+
???+ tip
18+
All examples shared in this documentation are available within the [project repository](https://github.com/awslabs/aws-lambda-powertools-python/tree/develop/examples){target="_blank"}.
19+
20+
You might need a custom middleware to abstract non-functional code. These are often custom authorization or any reusable logic you might need to run before/after a Lambda function invocation.
1221

13-
## Middleware with no params
22+
### Middleware with no params
1423

1524
You can create your own middleware using `lambda_handler_decorator`. The decorator factory expects 3 arguments in your function signature:
1625

1726
* **handler** - Lambda function handler
1827
* **event** - Lambda function invocation event
1928
* **context** - Lambda function context object
2029

21-
```python hl_lines="3-4 10" title="Creating your own middleware for before/after logic"
22-
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
30+
### Middleware with before logic
31+
32+
=== "getting_started_middleware_before_logic_function.py"
33+
```python hl_lines="5 23 24 29 30 32 37 38"
34+
--8<-- "examples/middleware_factory/src/getting_started_middleware_before_logic_function.py"
35+
```
36+
37+
=== "getting_started_middleware_before_logic_payload.json"
38+
39+
```json hl_lines="9-13"
40+
--8<-- "examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json"
41+
```
2342

24-
@lambda_handler_decorator
25-
def middleware_before_after(handler, event, context):
26-
# logic_before_handler_execution()
27-
response = handler(event, context)
28-
# logic_after_handler_execution()
29-
return response
43+
### Middleware with after logic
3044

31-
@middleware_before_after
32-
def lambda_handler(event, context):
33-
...
34-
```
45+
=== "getting_started_middleware_after_logic_function.py"
46+
```python hl_lines="7 14 15 21-23 37"
47+
--8<-- "examples/middleware_factory/src/getting_started_middleware_after_logic_function.py"
48+
```
3549

36-
## Middleware with params
50+
=== "getting_started_middleware_after_logic_payload.json"
51+
52+
```json
53+
--8<-- "examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json"
54+
```
55+
56+
### Middleware with params
3757

3858
You can also have your own keyword arguments after the mandatory arguments.
3959

40-
```python hl_lines="2 12" title="Accepting arbitrary keyword arguments"
41-
@lambda_handler_decorator
42-
def obfuscate_sensitive_data(handler, event, context, fields: List = None):
43-
# Obfuscate email before calling Lambda handler
44-
if fields:
45-
for field in fields:
46-
if field in event:
47-
event[field] = obfuscate(event[field])
60+
=== "getting_started_middleware_with_params_function.py"
61+
```python hl_lines="6 27 28 29 33 49"
62+
--8<-- "examples/middleware_factory/src/getting_started_middleware_with_params_function.py"
63+
```
64+
65+
=== "getting_started_middleware_with_params_payload.json"
4866

49-
return handler(event, context)
67+
```json hl_lines="18 19 20"
68+
--8<-- "examples/middleware_factory/src/getting_started_middleware_with_params_payload.json"
69+
```
70+
71+
## Advanced
72+
73+
For advanced use cases, you can instantiate [Tracer](../core/tracer.md) inside your middleware, and add annotations as well as metadata for additional operational insights.
5074

51-
@obfuscate_sensitive_data(fields=["email"])
52-
def lambda_handler(event, context):
53-
...
54-
```
75+
=== "advanced_middleware_tracer_function.py"
76+
```python hl_lines="7 9 12 16 17 19 25 42"
77+
--8<-- "examples/middleware_factory/src/advanced_middleware_tracer_function.py"
78+
```
5579

56-
## Tracing middleware execution
80+
=== "advanced_middleware_tracer_payload.json"
81+
82+
```json
83+
--8<-- "examples/middleware_factory/src/advanced_middleware_tracer_payload.json"
84+
```
85+
86+
![Middleware advanced Tracer](../media/middleware_factory_tracer_2.png)
87+
88+
### Tracing middleware **execution**
5789

5890
If you are making use of [Tracer](../core/tracer.md), you can trace the execution of your middleware to ease operations.
5991

6092
This makes use of an existing Tracer instance that you may have initialized anywhere in your code.
6193

62-
```python hl_lines="3" title="Tracing custom middlewares with Tracer"
63-
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
94+
???+ warning
95+
You must [enable Active Tracing](../core/tracer/#permissions) in your Lambda function when using this feature, otherwise Lambda cannot send traces to XRay.
6496

65-
@lambda_handler_decorator(trace_execution=True)
66-
def my_middleware(handler, event, context):
67-
return handler(event, context)
97+
=== "getting_started_middleware_tracer_function.py"
98+
```python hl_lines="8 14 15 36"
99+
--8<-- "examples/middleware_factory/src/getting_started_middleware_tracer_function.py"
100+
```
68101

69-
@my_middleware
70-
def lambda_handler(event, context):
71-
...
72-
```
102+
=== "getting_started_middleware_tracer_payload.json"
73103

74-
When executed, your middleware name will [appear in AWS X-Ray Trace details as](../core/tracer.md) `## middleware_name`.
104+
```json hl_lines="18 19 20"
105+
--8<-- "examples/middleware_factory/src/getting_started_middleware_tracer_payload.json"
106+
```
75107

76-
For advanced use cases, you can instantiate [Tracer](../core/tracer.md) inside your middleware, and add annotations as well as metadata for additional operational insights.
108+
When executed, your middleware name will [appear in AWS X-Ray Trace details as](../core/tracer.md) `## middleware_name`, in this example the middleware name is `## middleware_with_tracing`.
109+
110+
![Middleware simple Tracer](../media/middleware_factory_tracer_1.png)
111+
112+
### Combining Powertools utilities
113+
114+
You can create your own middleware and combine many features of Lambda Powertools such as [trace](../core/logger.md), [logs](../core/logger.md), [feature flags](feature_flags.md), [validation](validation.md), [jmespath_functions](jmespath_functions.md) and others to abstract non-functional code.
115+
116+
In the example below, we create a Middleware with the following features:
117+
118+
* Logs and traces
119+
* Validate if the payload contains a specific header
120+
* Extract specific keys from event
121+
* Automatically add security headers on every execution
122+
* Validate if a specific feature flag is enabled
123+
* Save execution history to a DynamoDB table
124+
125+
=== "combining_powertools_utilities_function.py"
126+
```python hl_lines="11 28 29 119 52 61 73"
127+
--8<-- "examples/middleware_factory/src/combining_powertools_utilities_function.py"
128+
```
129+
130+
=== "combining_powertools_utilities_schema.py"
131+
```python hl_lines="12 14"
132+
--8<-- "examples/middleware_factory/src/combining_powertools_utilities_schema.py"
133+
```
134+
135+
=== "combining_powertools_utilities_event.json"
136+
```python hl_lines="10"
137+
--8<-- "examples/middleware_factory/src/combining_powertools_utilities_event.json"
138+
```
77139

78-
```python hl_lines="6-8" title="Add custom tracing insights before/after in your middlware"
79-
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
80-
from aws_lambda_powertools import Tracer
81-
82-
@lambda_handler_decorator(trace_execution=True)
83-
def middleware_name(handler, event, context):
84-
# tracer = Tracer() # Takes a copy of an existing tracer instance
85-
# tracer.add_annotation...
86-
# tracer.add_metadata...
87-
return handler(event, context)
88-
```
140+
=== "SAM TEMPLATE"
141+
```python hl_lines="66 83 89 96 103 108-113 119 130"
142+
--8<-- "examples/middleware_factory/sam/combining_powertools_utilities_template.yaml"
143+
```
89144

90145
## Tips
91146

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
Description: Middleware-powertools-utilities example
4+
5+
Globals:
6+
Function:
7+
Timeout: 5
8+
Runtime: python3.9
9+
Tracing: Active
10+
Architectures:
11+
- x86_64
12+
Environment:
13+
Variables:
14+
LOG_LEVEL: DEBUG
15+
POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1
16+
POWERTOOLS_LOGGER_LOG_EVENT: true
17+
POWERTOOLS_SERVICE_NAME: middleware
18+
19+
Resources:
20+
MiddlewareFunction:
21+
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
22+
Properties:
23+
CodeUri: middleware/
24+
Handler: app.lambda_handler
25+
Description: Middleware function
26+
Policies:
27+
- AWSLambdaBasicExecutionRole # Managed Policy
28+
- Version: '2012-10-17' # Policy Document
29+
Statement:
30+
- Effect: Allow
31+
Action:
32+
- dynamodb:PutItem
33+
Resource: !GetAtt HistoryTable.Arn
34+
- Effect: Allow
35+
Action: # https://docs.aws.amazon.com/appconfig/latest/userguide/getting-started-with-appconfig-permissions.html
36+
- ssm:GetDocument
37+
- ssm:ListDocuments
38+
- appconfig:GetLatestConfiguration
39+
- appconfig:StartConfigurationSession
40+
- appconfig:ListApplications
41+
- appconfig:GetApplication
42+
- appconfig:ListEnvironments
43+
- appconfig:GetEnvironment
44+
- appconfig:ListConfigurationProfiles
45+
- appconfig:GetConfigurationProfile
46+
- appconfig:ListDeploymentStrategies
47+
- appconfig:GetDeploymentStrategy
48+
- appconfig:GetConfiguration
49+
- appconfig:ListDeployments
50+
- appconfig:GetDeployment
51+
Resource: "*"
52+
Events:
53+
GetComments:
54+
Type: Api
55+
Properties:
56+
Path: /comments
57+
Method: GET
58+
GetCommentsById:
59+
Type: Api
60+
Properties:
61+
Path: /comments/{comment_id}
62+
Method: GET
63+
64+
# DynamoDB table to store historical data
65+
HistoryTable:
66+
Type: AWS::DynamoDB::Table
67+
Properties:
68+
TableName: "HistoryTable"
69+
AttributeDefinitions:
70+
- AttributeName: customer_id
71+
AttributeType: S
72+
- AttributeName: request_id
73+
AttributeType: S
74+
KeySchema:
75+
- AttributeName: customer_id
76+
KeyType: HASH
77+
- AttributeName: request_id
78+
KeyType: "RANGE"
79+
BillingMode: PAY_PER_REQUEST
80+
81+
# Feature flags using AppConfig
82+
FeatureCommentApp:
83+
Type: AWS::AppConfig::Application
84+
Properties:
85+
Description: "Comments Application for feature toggles"
86+
Name: comments
87+
88+
FeatureCommentDevEnv:
89+
Type: AWS::AppConfig::Environment
90+
Properties:
91+
ApplicationId: !Ref FeatureCommentApp
92+
Description: "Development Environment for the App Config Comments"
93+
Name: dev
94+
95+
FeatureCommentConfigProfile:
96+
Type: AWS::AppConfig::ConfigurationProfile
97+
Properties:
98+
ApplicationId: !Ref FeatureCommentApp
99+
Name: features
100+
LocationUri: "hosted"
101+
102+
HostedConfigVersion:
103+
Type: AWS::AppConfig::HostedConfigurationVersion
104+
Properties:
105+
ApplicationId: !Ref FeatureCommentApp
106+
ConfigurationProfileId: !Ref FeatureCommentConfigProfile
107+
Description: 'A sample hosted configuration version'
108+
Content: |
109+
{
110+
"save_history": {
111+
"default": true
112+
}
113+
}
114+
ContentType: 'application/json'
115+
116+
# this is just an example
117+
# change this values according your deployment strategy
118+
BasicDeploymentStrategy:
119+
Type: AWS::AppConfig::DeploymentStrategy
120+
Properties:
121+
Name: "Deployment"
122+
Description: "Deployment strategy for comments app."
123+
DeploymentDurationInMinutes: 1
124+
FinalBakeTimeInMinutes: 1
125+
GrowthFactor: 100
126+
GrowthType: LINEAR
127+
ReplicateTo: NONE
128+
129+
ConfigDeployment:
130+
Type: AWS::AppConfig::Deployment
131+
Properties:
132+
ApplicationId: !Ref FeatureCommentApp
133+
ConfigurationProfileId: !Ref FeatureCommentConfigProfile
134+
ConfigurationVersion: !Ref HostedConfigVersion
135+
DeploymentStrategyId: !Ref BasicDeploymentStrategy
136+
EnvironmentId: !Ref FeatureCommentDevEnv
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import time
2+
from typing import Callable
3+
4+
import requests
5+
from requests import Response
6+
7+
from aws_lambda_powertools import Tracer
8+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
9+
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
10+
from aws_lambda_powertools.utilities.typing import LambdaContext
11+
12+
tracer = Tracer()
13+
app = APIGatewayRestResolver()
14+
15+
16+
@lambda_handler_decorator(trace_execution=True)
17+
def middleware_with_advanced_tracing(handler, event, context) -> Callable:
18+
19+
tracer.put_metadata(key="resource", value=event.get("resource"))
20+
21+
start_time = time.time()
22+
response = handler(event, context)
23+
execution_time = time.time() - start_time
24+
25+
tracer.put_annotation(key="TotalExecutionTime", value=str(execution_time))
26+
27+
# adding custom headers in response object after lambda executing
28+
response["headers"]["execution_time"] = execution_time
29+
response["headers"]["aws_request_id"] = context.aws_request_id
30+
31+
return response
32+
33+
34+
@app.get("/products")
35+
def create_product() -> dict:
36+
product: Response = requests.get("https://dummyjson.com/products/1")
37+
product.raise_for_status()
38+
39+
return {"product": product.json()}
40+
41+
42+
@middleware_with_advanced_tracing
43+
def lambda_handler(event: dict, context: LambdaContext) -> dict:
44+
return app.resolve(event, context)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"resource": "/products",
3+
"path": "/products",
4+
"httpMethod": "GET"
5+
}

0 commit comments

Comments
 (0)