aws / aws-sam-cli

CLI tool to build, test, debug, and deploy Serverless applications using AWS SAM
https://aws.amazon.com/serverless/sam/
Apache License 2.0
6.47k stars 1.16k forks source link

Feature request: Support Lambda Function Urls in local start-api #4299

Open lambrospetrou opened 1 year ago

lambrospetrou commented 1 year ago

Describe your idea/feature/enhancement

With the addition of Lambda Function URLs, we don't need API Gateway for full APIs. However, the sam local start-api command does not support that yet, and fails with the error that no API exists.

An example of such SAM template can be found in https://github.com/lambrospetrou/aws-playground/blob/master/aws-lambda-url-and-fly-golang-proxy/aws-iac/sam-template.yml. Relevant excerpt below:

Resources:
  HelloFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ../build/handler.zip
      Handler: handler
      Runtime: go1.x
      MemorySize: 512
      FunctionUrlConfig:
        AuthType: NONE

Proposal

Ideally, the cli should be able to add the Function URLs defined as part of the expose API, or even expose them in different ports if we want them separate from a potential API Gateway definition.

Things to consider:

  1. Will this require any updates to the SAM Spec. It shouldn't.
lucashuy commented 1 year ago

Thanks for raising this feature request! I'll bring this up with the team to discuss about next steps

jamalmoir commented 1 year ago

This is a pretty big blocker for me. Not being able to develop locally without deploying test functions is very annoying.

Great to hear it's being looked into.

JP-tech-sh commented 1 year ago

This could be a really nice feature to implement, I tried to use a local template file with a dummy api gateway event, this may work for some but the main problem is the Payload format version, in the case of a regular lambda with lambda url function, the version is 2.0 and when executing sam local start-api the only version available is 1.0, there should not be many changes requierd to make this work, adding support for reading the lambda url details form the template file and using payload v2.0 when this is detected, I'll be happy to help with this.

praneetap commented 1 year ago

@JP-tech-sh thanks for offering to contribute! Would you be willing to write up a design proposal in this issue? Once we have clarity on design you can open a PR.

valerena commented 1 year ago

I think one difference between Function URLs and API Gateway is that functions URLs are all served directly at the root / (The difference between multiple functions is just the domain).

What would be a good experience for you when you have multiple functions with URLs on the same template? Options I can think of:

lambrospetrou commented 1 year ago

As a user I would prefer to have a different port per Lambda function URL, and APIGW. Having different prefixes in the path would work if all you do is call it once, but when pointing to the API from another application (e.g. a UI) where the paths are assuming to be at the / of the endpoint then it makes this awkward.

Also, the user should be able to specify by resource name/id the lambda function to start so that you only start that one, which I think is a behavior that already exists for the single invocation functionality.

kulanjith-deelaka-appspotr commented 1 year ago

this may work for some but the main problem is the Payload format version, in the case of a regular lambda with lambda url function, the version is 2.0 and when executing sam local start-api the only version available is 1.0

@JP-tech-sh It's a really valid point that you have raised here. This is the major issue that impacts usability, hopefully the payload version is an easy fix, this would make it bearable for the time being and ultimately hope this Feature Request goes through.

AxelJunker commented 1 year ago

I would also like this feature!

As a workaround I use sam local start-lambda, then invoke the function (which has the FunctionUrlConfig field in the template) with:

curl -XPOST "http://127.0.0.1:3001/2015-03-31/functions/MyFunction/invocations" -d '{"payload":"hello world!"}'

No idea why 2015-03-31 is needed (it doesn't work when I change it).

Sources: https://www.reddit.com/r/aws/comments/x2i1x2/comment/imjjuhd/?utm_source=share&utm_medium=web2x&context=3 https://hub.docker.com/r/amazon/aws-lambda-java

sayandcode commented 1 year ago

I would also like this feature!

As a workaround I use sam local start-lambda, then invoke the function (which has the FunctionUrlConfig field in the template) with:

curl -XPOST "http://127.0.0.1:3001/2015-03-31/functions/MyFunction/invocations" -d '{"payload":"hello world!"}'

No idea why 2015-03-31 is needed (it doesn't work when I change it).

Sources: https://www.reddit.com/r/aws/comments/x2i1x2/comment/imjjuhd/?utm_source=share&utm_medium=web2x&context=3 https://hub.docker.com/r/amazon/aws-lambda-java

Thanks a lot for that workaround! This solution looks good enough for me to run on my local. Do you know how to get methods other than POST to work?

cShirley14 commented 1 year ago

I would also like this feature!

As a workaround I use sam local start-lambda, then invoke the function (which has the FunctionUrlConfig field in the template) with:

curl -XPOST "http://127.0.0.1:3001/2015-03-31/functions/MyFunction/invocations" -d '{"payload":"hello world!"}'

No idea why 2015-03-31 is needed (it doesn't work when I change it).

Sources: https://www.reddit.com/r/aws/comments/x2i1x2/comment/imjjuhd/?utm_source=share&utm_medium=web2x&context=3 https://hub.docker.com/r/amazon/aws-lambda-java

FWIW the date 2015-03-31 is AWS's way of versioning (totally random, but they articulate this in different language docs PHP , Node etc...

darrenkidd commented 1 year ago

Extending @AxelJunker's method, if you used the sam CLI and a template to build your solution you probably have an events/event.json you can play with.

I found this worked quite well to invoke the lambda directly (my lambda returns HTML):

$ curl -XPOST "http://127.0.0.1:3001/2015-03-31/functions/MyFunction/invocations" -d @events/event.json
{"statusCode": 200, "headers": {"Content-Type": "text/html"}, "body": "..."}

However, I couldn't execute that in the browser. So I threw together a quick Flask app to forward requests so I could try it locally. Essentially this is answering @sayandcode's question as well.

requirements.txt

flask
requests

app.py

import os
import requests

from flask import Flask, request, Response

LAMBDA_HOST = os.getenv('LAMBDA_HOST', '127.0.0.1:3001')
TARGET_URL = 'http://{}/2015-03-31/functions/{}/invocations'

app = Flask(__name__)

@app.route('/<function>', methods=['GET'])
def forward_request(function):
    url = TARGET_URL.format(LAMBDA_HOST, function)
    payload = {
        'httpMethod': 'GET',
        'queryStringParameters': request.args.to_dict(),
    }
    response = requests.post(url, json=payload)
    response.raise_for_status()
    lambda_response = response.json()
    return Response(lambda_response['body'], status=lambda_response['statusCode'], headers=lambda_response['headers'])

There's way more you can do with the payload etc. but this was all I really needed. I just started everything then went in to the browser and hit http://127.0.0.1:5000/MyFunction?a=b as a test.

Forwarder:

$ flask run
...
127.0.0.1 - - [19/Jun/2023 21:42:10] "GET /MyFunction?a=b HTTP/1.1" 200 -

Lambda:

$ sam local start-lambda --force-image-build
...
START RequestId: d88b7596-5bd8-4be0-9703-3249c6645e4d Version: $LATEST
{'httpMethod': 'GET', 'queryStringParameters': {'a': 'b'}}
END RequestId: d88b7596-5bd8-4be0-9703-3249c6645e4d
REPORT RequestId: d88b7596-5bd8-4be0-9703-3249c6645e4d  Init Duration: 0.22 ms  Duration: 182.33 ms     Billed Duration: 183 ms Memory Size: 128 MB     Max Memory Used: 128 MB
2023-06-19 21:41:31 127.0.0.1 - - [19/Jun/2023 21:41:31] "POST /2015-03-31/functions/MyFunction/invocations HTTP/1.1" 200 -

HTH

It'd definitely be much nicer to have this built in somehow, however. I'll have a look at the code at some point and see if there's any obvious way of integrating it.

pharsi commented 8 months ago

I ran into this right now, using Lambda Function integrated with REST API GW. Will share a template that's causing this as I get time.

The issue is, when I deploy Lambda to AWS using sam template the payload format version is 2.0 while on local debugging setup the payload format version is 2.0, this is causing some nightmares in local debugging. For instance, pathParameters and rawQueryString are different in both format versions

leehagoodjames commented 8 months ago

I am also running into this now. I tried the Python Flask app and it works for making a request (with query parameters) directly from the browser (and curl), but I am getting a cors error when my local dev setup calls the API

leehagoodjames commented 8 months ago

I managed to fix the CORS issue by rewriting the last line in app.py to merge the lambda headers dictionariy with {'Access-Control-Allow-Origin': '*'} via the following:

return Response(lambda_response['body'], status=lambda_response['statusCode'], headers=lambda_response['headers'] | {'Access-Control-Allow-Origin': '*'})
faermanj commented 1 month ago

This would be super helpful, for some bizarre reason our API Gateway integration just stopped working and we would love to use anything else.