-
Notifications
You must be signed in to change notification settings - Fork 0
/
lambda_handler.go
193 lines (176 loc) · 6.24 KB
/
lambda_handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// Package mantil integrates Lambda function with API's in a Mantil project.
//
// It is similar to the default AWS Go [Lambda function
// handler](https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html). The
// main difference is that mantil.go handler mantil.LmabdaHandler accepts struct
// instance and exposes each exported method of that struct. Where the default
// implementation has a single function as an entrypoint.
//
// Package is intended for usage inside Mantil project.
//
// Package also provides simple key value store interface backed by a DynamoDB table.
// It manages that table as part of the Mantil project. It is created on demand and
// destroyed with the project.
package mantil
import (
"context"
"log"
"net/http"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-lambda-go/lambdacontext"
"github.com/mantil-io/mantil.go/logs"
)
type lambdaHandler struct {
caller *caller
requestNo int
}
func newHandler(api interface{}) *lambdaHandler {
return &lambdaHandler{
caller: newCaller(api),
}
}
// LambdaHandler is entrypoint for Mantil Lambda functions.
// Use it in your Lambda functions:
// mantil.LambdaHandler(api)
// where api is Go struct. All exported methods of the api struct will be
// exposed as API Gateway HTTP methods.
// Exported methods must follow this rules:
//
// * may take between 0 and two arguments.
// * if there are two arguments, the first argument must satisfy the "context.Context" interface.
// * may return between 0 and two arguments.
// * if there are two return values, the second argument must be an error.
// * if there is one return value it must be an error.
//
// valid signatures are:
//
// func ()
// func () error
// func (TIn) error
// func () (TOut, error)
// func (context.Context) error
// func (context.Context, TIn) error
// func (context.Context) (TOut, error)
// func (context.Context, TIn) (TOut, error)
//
// For example of Lambda function see this example:
// https://github.com/mantil-io/template-excuses/blob/master/functions/excuses/main.go
//
// That defines Lambda handler around this Go struct:
// https://github.com/mantil-io/template-excuses/blob/master/api/excuses/excuses.go
//
// When used with API Gateway in Mantil application exported methods are exposed at URLs:
// Default - [root]/excuses
// Count - [root]/excuses/count
// Random - [root]/excuses/random
// ... and so on, where excuses is the name of this api.
//
// This is similar to the default Go Lambda integration: https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html
// With added feature that all struct exported methods all exposed.
//
// Context provided to the methods is RequestContext which is wrapper around
// default lambdacontext with few added attributes.
//
// If you are using AWS Console and test calling Lambda functions use this test data:
// {
// "uri": "count",
// "payload": ...
// }
// to call count method for example.
func LambdaHandler(api interface{}) {
handler := newHandler(api)
lambda.StartHandler(handler)
}
// Invoke implements lambda.Handler interface required by lambda.StartHandler in LmabdHandler function.
func (h *lambdaHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) {
return h.formatResponse(h.invoke(ctx, payload))
}
func (h *lambdaHandler) invoke(ctx context.Context, payload []byte) (Request, response) {
req := parseRequest(payload)
reqCtx := h.initContext(ctx, &req)
cb, err := logs.LambdaResponse(req.Headers)
if err != nil {
info("failed to start nats lambda response: %v", err)
return req, errResponse(err, http.StatusInternalServerError)
}
rsp := h.caller.call(reqCtx, req.Body, req.Params, req.Methods...)
if err := rsp.Err(); err != nil {
info("invoke of method %v failed with error: %v", req.Methods, err)
}
if cb != nil {
cb(rsp.Value(), rsp.Err())
}
return req, rsp
}
func (h *lambdaHandler) formatResponse(req Request, rsp response) ([]byte, error) {
switch req.Type {
case APIGateway:
return rsp.AsAPIGateway()
case Streaming:
rm, err := rsp.AsStreaming(req)
if err != nil {
return nil, err
}
return nil, toWsForwarder(rm)
case WSConnect, WSDisconnect, WSMessage:
return rsp.AsWS()
default:
return rsp.Raw()
}
}
func (h *lambdaHandler) initContext(ctx context.Context, req *Request) context.Context {
h.requestNo++
log.SetFlags(log.Llongfile) // no need for timestamp, that will add cloudwatch
cv := RequestContext{
RequestNo: h.requestNo,
Request: *req,
}
lc, ok := lambdacontext.FromContext(ctx)
if ok {
cv.Lambda = lc
// move custom headers to request
if custom := lc.ClientContext.Custom; len(custom) > 0 {
if req.Headers == nil {
req.Headers = make(map[string]string)
}
for k, v := range lc.ClientContext.Custom {
req.Headers[k] = v
}
cv.Request = *req
}
}
return context.WithValue(ctx, contextKey, &cv)
}
// RequestContext is provided as first context attribute to all api methods handled by mantil.go
// You can get it by mantil.FromContext().
// It is wrapper around github.com/aws/aws-lambda-go/lambdacontext
type RequestContext struct {
// Number of same worker Lambda function invocations
// 1 - cold start
RequestNo int
// Lambda Request attributes
Request Request
// Ref: https://pkg.go.dev/github.com/aws/[email protected]/lambdacontext#LambdaContext
Lambda *lambdacontext.LambdaContext
}
// Authorizer attributes.
// This is place where awuthorizer on API Gateway stores his metadata.
func (r *RequestContext) Authorizer() map[string]interface{} {
return r.Request.attr.RequestContext.Authorizer
}
// WSConnectionID if the request is received through Websocket API Gateway this will return ID.
func (r *RequestContext) WSConnectionID() string {
return r.Request.attr.RequestContext.ConnectionID
}
// An unexported type to be used as the key for types in this package.
// This prevents collisions with keys defined in other packages.
type key struct{}
// The key for a LambdaContext in Contexts.
// Users of this package must use lambdacontext.NewContext and lambdacontext.FromContext
// instead of using this key directly.
var contextKey = &key{}
// FromContext returns the LambdaContext value stored in ctx, if any.
func FromContext(ctx context.Context) (*RequestContext, bool) {
lc, ok := ctx.Value(contextKey).(*RequestContext)
return lc, ok
}