aws / aws/aws-sam-cli

Bug: Inconsistent behavior when using HTTP API in local environment vs Lambda

Open
#5,860 5 comments 3 reactions 0 assignees View on GitHub
area/local/start-api type/feature
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.