-
Notifications
You must be signed in to change notification settings - Fork 557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Lambda Function URL HTTP request and response types #436
Conversation
Codecov Report
@@ Coverage Diff @@
## main #436 +/- ##
=======================================
Coverage 71.42% 71.42%
=======================================
Files 19 19
Lines 1050 1050
=======================================
Hits 750 750
Misses 232 232
Partials 68 68 Continue to review full report at Codecov.
|
events/lambda_http.go
Outdated
package events | ||
|
||
// LambdaHTTPRequest contains data coming from the new HTTP API Gateway | ||
type LambdaHTTPRequest struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm gonna change this to LambdaFunctionURLRequest/Response to align with the feature name in the documentation
events/lambda_http.go
Outdated
type LambdaHTTPRequestContext struct { | ||
RouteKey string `json:"routeKey"` // RouteKey is expected to always be `"$default"` | ||
AccountID string `json:"accountId"` | ||
Stage string `json:"stage"` // Stage is expected to always be `"$default"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm going to delete this too, docs are pretty clear that it's a vestigial field.
events/lambda_http.go
Outdated
|
||
// LambdaHTTPRequestContext contains the information to identify the AWS account and resources invoking the Lambda function. | ||
type LambdaHTTPRequestContext struct { | ||
RouteKey string `json:"routeKey"` // RouteKey is expected to always be `"$default"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto, thinking to delete this
For ref. here's the equivalent change in the Java events lib: aws/aws-lambda-java-libs#320 |
Headers map[string]string `json:"headers"` | ||
QueryStringParameters map[string]string `json:"queryStringParameters,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be map[string][]string
or similar to events.APIGatewayProxyRequest. (Same comment for LambdaFunctionURLResponse
)
Bringing this up mainly because both HTTP header and URL query strings can have multiple keys of the same value.
Header:
x-foo-bar: abc123
x-foo-bar: efg456
x-foo-bar: hij789, lkm000
query:
?foo=abc123&foo=efg456&foo=hij789&foo=lkm000
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lambda Function URLs ended up copying the API Gateway payload 2.0 shape, which I believe got rid of the MultiValue*** option - events.APIGatewayV2HTTPRequest
I'll double check what the Lambda Function URLs behavior is when a client sends multi value headers/query, maybe there is the option for a custom MarshalJSON/UnmarshalJSON so that the struct consumer can still get a map[string][]string
- is that what you had in mind @jasdel ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
verified that the function urls respects multi-value query string and multi-value headers, but concatenates with ,
rather than representing as an array in the json
my echo function:
def lambda_handler(event, _):
return event
my test case:
~/test
$ cat furl.go
package main
import (
"bytes"
"io"
"net/http"
"os"
)
func main() {
url := "https://sbehoygxywlg7dihj4baeak4im0sbkyf.lambda-url.us-west-2.on.aws/yolo?hello=world&hello=lambda&foo=bar"
body := bytes.NewBuffer([]byte("hello"))
req, _ := http.NewRequest("POST", url, body)
req.Header.Add("whats", "up")
req.Header.Add("hello", "world")
req.Header.Add("hello", "lambda")
res, _ := http.DefaultClient.Do(req)
io.Copy(os.Stdout, res.Body)
}
~/test
$ go run furl.go | jq
{
"headers": {
"whats": "up",
"x-amzn-trace-id": "Root=1-62512b3e-7cc00b5e15c31ddd5201e2b6",
"x-forwarded-proto": "https",
"host": "sbehoygxywlg7dihj4baeak4im0sbkyf.lambda-url.us-west-2.on.aws",
"x-forwarded-port": "443",
"hello": "world,lambda",
"x-forwarded-for": "205.251.233.105",
"accept-encoding": "gzip",
"user-agent": "Go-http-client/1.1"
},
"isBase64Encoded": true,
"rawPath": "/yolo",
"routeKey": "$default",
"requestContext": {
"accountId": "anonymous",
"timeEpoch": 1649486654761,
"routeKey": "$default",
"stage": "$default",
"domainPrefix": "sbehoygxywlg7dihj4baeak4im0sbkyf",
"requestId": "274319d8-362b-488d-af20-1350781093f9",
"domainName": "sbehoygxywlg7dihj4baeak4im0sbkyf.lambda-url.us-west-2.on.aws",
"http": {
"path": "/yolo",
"protocol": "HTTP/1.1",
"method": "POST",
"sourceIp": "205.251.233.105",
"userAgent": "Go-http-client/1.1"
},
"time": "09/Apr/2022:06:44:14 +0000",
"apiId": "sbehoygxywlg7dihj4baeak4im0sbkyf"
},
"queryStringParameters": {
"foo": "bar",
"hello": "world,lambda"
},
"body": "aGVsbG8=",
"version": "2.0",
"rawQueryString": "hello=world&hello=lambda&foo=bar"
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I came up with this approach to make the .Headers
field more easily assignable to the stdlib http.Headers
type. The same could be done for url.Values
for the .QueryStringParameters
field
type LambdaFunctionURLResponse struct {
// ...
Headers functionURLHeaders `json:"headers"`
// ...
}
type functionURLHeaders http.Header
func (headers *functionURLHeaders) UnmarshalJSON(b []byte) error {
var intermediate map[string]commaSeperatedValues
if err := json.Unmarshal(b, &intermediate); err != nil {
return err
}
*headers = make(functionURLHeaders, len(intermediate))
for k, v := range intermediate {
(*headers)[k] = v
}
return nil
}
type commaSeperatedValues []string
func (values *commaSeperatedValues) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
*values = strings.Split(s, ",")
return nil
}
func (values commaSeperatedValues) MarshalJSON() ([]byte, error) {
return json.Marshal(strings.Join(values, ","))
}
Which was neat, but I'm not 100% convinced this is the right approach for this struct. I'd hoped instead to be able to legally cast like var values map[string]commaSeperatedValue; headers := http.Header(values)
, but go's type system seemed to demand the make(...)
and re-assignment loop, which felt marginally wasteful.
I poked @carlzogh again about this, and right now I'm leaning to keep this PR as-is rather than extending the serialization.
- For the most part, structs in this library, and types in the Java and .NET libraries, mirror the implicit JSON schema
- Documentation claims that Lambda function URLs are compatible with API Gateway HTTP Payload Version 2.0 - the struct for this type does not customize the
.Header
or.QueryStringParameters
fields.
longer term, I'm open to smarter serialization for the http proxy event sources, as either some v2
types with a richer set of built-in types and appropriate documentation for construction, or with a higher abstraction layer that hides the json goo behind the stdlib http.Request/http.ResponseWriter
types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current implementation that joins multiple headers makes sense, given the API gateway 2.0 style. I'd suggestion not added automatic splitting on commas though, because comma being a delimiter in a header is dependent on the header, and requires domain knowledge.
For example a common date format, HTTP-Date
, Wed, 21 Oct 2015 07:28:00 GMT
. Contains a comma in its value. Logic that splits on commas without domain knowledge of the header would produce the wrong result. Leaving header value splitting to the consumer probably is the best path forward.
Description of changes:
https://aws.amazon.com/blogs/aws/announcing-aws-lambda-function-urls-built-in-https-endpoints-for-single-function-microservices/
lifted sample data from https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html
structs are based on the APIGatewayV2 events, but for the subset of features that Lambda Function URLs do not support, where sample data has
null
, I removed the fields - in particular this affected theAuthorizer
struct tree removingLambda
andJWT
types, and eliminatedAuthentication
from the request context struct.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.