Bug: Inconsistent behavior when using HTTP API in local environment vs Lambda
- Dominant language
- Python
- Stars
- 6.7k
- Forks
- 1.2k
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 52
Description
### Description:
Hello everybody! Following @moelasmar's recommendation in issue #5579, I am opening a new issue with more information about the case. Along with Ruben, I'm building Powertools for AWS Lambda and a customer has raised this issue: aws-powertools/powertools-lambda-python#2765.
I'll go into more detail throughout this issue, but to summarize: the v2.0 payload of an HTTP API (`AWS::Serverless::HttpApi`) behaves differently when using `sam local start-api` and when running in the API Gateway + Lambda environment on AWS.
In order not to focus on a specific tool, I will not consider the use of Powertools here, but only payloads. I think it will be easier to understand if this is expected behavior from the SAM CLI or a possible bug.
### Steps to reproduce:
I'm using the following SAM template with a specific **StageName** for **HttpApi**:
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app
Globals:
Function:
Timeout: 5
MemorySize: 128
Runtime: python3.10
Resources:
HttpApi:
Type: AWS::Serverless::HttpApi
Properties:
StageName: prod
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
Handler: app.lambda_handler
CodeUri: hello_world
Description: Hello World function
Architectures:
- x86_64
Tracing: Active
Events:
HelloPath:
Type: HttpApi
Properties:
ApiId: !Ref HttpApi
Path: /hello
Method: GET
```
And I have the following Lambda code:
```python
import json
def lambda_handler(event: dict, context) -> dict:
print(event)
```
The requirements.txt is the basic with only `requests` library and I'm running `sam build` and `sam local start-api`
```sh
❯ sam local start-api
Initializing the lambda functions containers.
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.10-rapid-x86_64.
Mounting /home/leandro/DEVEL-PYTHON/tmp/sam-api-httpapi/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container
Containers Initialization is done.
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. If you used sam build before running local commands, you will need to re-run sam build for the changes to be picked up.
You only need to restart SAM CLI if you update your AWS SAM template
2023-08-28 23:40:40 WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:3000
```
After that, I invoked the URL `http://127.0.0.1:3000/hello` just to print the payload I get in the Lambda function.
### Observed result:
When running it locally, I see that rawPath and path are just `/hello`, it's not adding the **stage name**. When I run this in the API Gateway + Lambda environment I see that the rawPath and path are `/prod/hello`, that is, the stage name is added.
Below are the payloads received in both environments. An important point to report here is that in API Gateway I deployed this stack using the default APIGateway URL (....execute-api.us-east-1.amazonaws.com) and with a custom domain. In both scenarios the behavior are the same: the stage is added to the rawPath and path.
**Payload using SAM CLI**
```json
{
"body": "",
"cookies": [],
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "deflate, gzip",
"Accept-Language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"Host": "127.0.0.1:3000",
"Sec-Ch-Ua": "\"Google Chrome\";v=\"113\", \"Chromium\";v=\"113\", \"Not-A.Brand\";v=\"24\"",
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": "\"Linux\"",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36",
"X-Forwarded-Port": "3000",
"X-Forwarded-Proto": "http"
},
"isBase64Encoded": false,
"pathParameters": {},
"rawPath": "/hello",
"rawQueryString": "",
"requestContext": {
"accountId": "123456789012",
"apiId": "1234567890",
"domainName": "localhost",
"domainPrefix": "localhost",
"http": {
"method": "GET",
"path": "/hello",
"protocol": "HTTP/1.1",
"sourceIp": "127.0.0.1",
"userAgent": "Custom User Agent String"
},
"requestId": "65df69cd-ac5d-4f41-8257-126097e297e1",
"routeKey": "GET /hello",
"stage": "prod",
"time": "28/Aug/2023:17:08:18 +0000",
"timeEpoch": 1693242498
},
"routeKey": "GET /hello",
"stageVariables": null,
"version": "2.0"
}
```
**Payload using API Gateway + Lambda**
```json
{
"version":"2.0",
"routeKey":"GET /hello",
"rawPath":"/prod/hello",
"rawQueryString":"",
"headers":{
"accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-encoding":"deflate, gzip",
"accept-language":"pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
"content-length":"0",
"host":"--REDACTED--",
"sec-ch-ua":"\"Google Chrome\";v=\"113\", \"Chromium\";v=\"113\", \"Not-A.Brand\";v=\"24\"",
"sec-ch-ua-mobile":"?0",
"sec-ch-ua-platform":"\"Linux\"",
"sec-fetch-dest":"document",
"sec-fetch-mode":"navigate",
"sec-fetch-site":"none",
"sec-fetch-user":"?1",
"upgrade-insecure-requests":"1",
"user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36",
"x-amzn-trace-id":"Root=1-64ed1d2b-321767c34bdc22f96d2d3798",
"x-forwarded-for":"--REDACTED--",
"x-forwarded-port":"443",
"x-forwarded-proto":"https"
},
"requestContext":{
"accountId":"--REDACTED--",
"apiId":"--REDACTED--",
"domainName":"--REDACTED--.cloud",
"domainPrefix":"testapigw",
"http":{
"method":"GET",
"path":"/prod/hello",
"protocol":"HTTP/1.1",
"sourceIp":"--REDACTED--",
"userAgent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
},
"requestId":"KZF-2gADoAMEPKg=",
"routeKey":"GET /hello",
"stage":"prod",
"time":"28/Aug/2023:22:18:19 +0000",
"timeEpoch":1693261099547
},
"isBase64Encoded":false
}
```
### Expected result:
When running in a local environment with `sam local start-api` it is expected to behave the same as when running in an AWS environment. The user can rely on the `rawPath` and `path` fields to make decisions such as which internal function to call, which route to map (in micro-frameworks, for example), among other things. I don't know if this is the expected behavior of the SAM CLI, but I think the experience should be the same when running it locally.
### Additional environment details (Ex: Windows, Mac, Amazon Linux etc)
```json
{
"version": "1.95.0",
"system": {
"python": "3.11.3",
"os": "Linux-5.14.10-300.fc35.x86_64-x86_64-with-glibc2.35"
},
"additional_dependencies": {
"docker_engine": "20.10.16",
"aws_cdk": "2.77.0 (build 06a0b19)",
"terraform": "1.1.9"
},
"available_beta_feature_env_vars": [
"SAM_CLI_BETA_FEATURES",
"SAM_CLI_BETA_BUILD_PERFORMANCE",
"SAM_CLI_BETA_TERRAFORM_SUPPORT",
"SAM_CLI_BETA_RUST_CARGO_LAMBDA"
]
}
```
Thank you very much for your attention. I hope that we can clarify this things and perhaps improve the user experience even more.
Contributor guide
Assessment
This issue has not been assessed yet.