Idea: AWS Lambda gateway plugin

Hi folks,

I’ve been playing around with AWS Lambda and API Gateway. Lambda is pretty nifty, but API Gateway is (imho) a beast, and introduces quite a bit of wiring complexity.

I’m tossing around the idea of writing an alternate HTTP gateway for Lambda, and I know Go, so a Caddy plugin seems like a possibility. Before I started I wanted to get some feedback on the idea and see if anyone else would find it useful.

Lambda has no notion of headers, so that creates some issues when you want to put a HTTP gateway in front of it. HTTP headers, status codes, and method names are all pretty critical bits of info. API Gateway has a (again, imho) fairly boroque mechanism for injecting request headers into the the Lambda request, and out the Lambda response. You have to wire up these rules for every lambda you expose. It’s all fairly complicated.

I’m envisioning something much simpler: Design a standard envelope format that would be used to wrap the HTTP request metadata (method, path, headers) and request body and invoke the lambda using this envelope. Expect the labmda to respond with a similar envelope that can be used to set the response status code, headers, and body.

If you want your Lambda to support the HTTP gateway, you need to write it so that it knows how to optionally handle this envelope.

This would enable a fairly simple Caddyfile directive that could be used to reverse proxy any Lambda function without special configuration for each. For example:

awslambda /lambda/ {
    # access/secret optional - default to standard AWS env vars
    aws_access   access-key-here
    aws_secret   secret-key-here
    # default to us-east-1 perhaps
    aws_region   us-west-2
    # lambda qualifier - can be used to target execution to an alias
    # which is useful for test/stage/prod env separation
    qualifier    prod

This would parse the function name from the path. For example: /lambda/hello would invoke the hello lambda function. /lambda/my-other-fx would invoke the my-other-fx function.

The standard Invoke operation would be used from the plugin (see:

Any high level thoughts on this approach? If folks like it, then the main questions are:

  • Envelope format (perhaps there’s a similar spec we can use)
  • Caddyfile options


– James


Sounds cool. Perhaps these endpoints would need to be protected? Even with something as simple as basicauth?

Sorry for my Caddy ignorance - does Caddy allow rules like that to be composed? For example, could you make a basic auth rule for the same /lambda/ path? Or is only one plugin run?

Yes, so you can use both basicauth and awslambda in your Caddyfile to protect the awslambda endpoint.

1 Like

Ok great. Any opinions about the message format? Considerations include:

  • Easy for receiver to cheaply determine if the message is in our wrapped/envelope format vs a raw JSON/XML message.
  • Easy for receiver to parse
  • Includes all HTTP metadata (method, path, headers, body)
  • Doesn’t make assumptions about the wrapped body’s format
  • Ideally: can be used identically as the response format

Not sure if these types of design discussions should go here - or if I should open a GitHub issue for discussion (could get a wider audience).

A very simple format could be something like this:

{ "method": "POST", "path": "/foo/bar", "headers": { } }
<HTTP request body here>

1st line is our message format name and version + CRLF
2nd line is a JSON object containing all HTTP metadata + CRLF
Remainder of message is the HTTP request body (if present)

Responses would be similar, with a few different fields in the meta JSON object:

{ "status": 200, "headers": { } }
<response body here>

I’m probably reinventing some wheel here, but this seems relatively straightforward for everyone. It does require that receivers have a JSON parser available, but for AWS Lambda I believe that’s true for all 3 runtimes (Python, node.js, and the Lambda Java lib all have JSON parsers).


– James

Following up on this thread. I’ve had some time to work on this and have a rough branch ready. I’m going to add godocs and additional tests. A few questions before I open the PR:

  1. How are external deps managed? My changes require I didn’t see an obvious place to wire that into the build system.
  2. It looks like plugin docs are here: – do you require a PR there as well before merging a new plugin?

Overall writing the plugin was painless. Thanks for providing a solid foundation for this type of work.

my best,

– James


The build server keeps its own cache of dependencies until we (I) manually update them.

Yep! :slight_smile:

Thanks for working on it!

Great. A couple of follow up questions:

  1. How does a new dependency get installed on the build server? Is there a bash script with the relevant ‘go get’ commands somewhere?
  2. Is there a mocking library already in use on this project (e.g. gomock)? I have one function that relies on the AWS LambdaClient interface - I could either pull in a tool like gomock, or introduce my own interface that wraps the AWS lib and provide a fake in my test. Do you have a preference on how to handle this?


– James

Basically to add your plugin, I do a go get on your plugin’s package path - I never use -u which tends to break things. :slight_smile: So in order to update a dependency later, I first delete it, then run go get, which gets all dependencies but doesn’t update ones that already are on disk.

I would prefer you keep things as lightweight and free of dependencies as possible. I don’t know what size of interface we’re talking about with AWS, but typically that’s what I’d recommend for anyone. (I’ve used interfaces to mock/test calls to AWS functions before. The interfaces need only be as big as how many methods you’re calling.)

Hello Caddy and AWS Lambda fans,

I have this plugin pretty much ready. I spent some time today writing docs and opened this PR:

Perhaps this conversation should move to that PR. There are a bunch of decisions I made around the envelope JSON format that others may have opinions on.

Once we get the “what” figured out (in the form of final docs that folks are happy with) then I’ll open a PR with the plugin and we can scrutinize the implementation.

I just didn’t want two PRs going at the same time and I figure we should get the bike sheddy bits like JSON field naming sorted before we get into whether or not it actually works.

My best,

– James


I use this to parse JSON: JSON Formatter