Skip to content

Commit b93410c

Browse files
committed
Add support non-http lambda event passthrough
1 parent c885fc2 commit b93410c

File tree

4 files changed

+157
-50
lines changed

4 files changed

+157
-50
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ func echoReplyHandler(w http.ResponseWriter, r *http.Request) {
3030
}
3131
```
3232

33+
## Pass through non-http events
34+
35+
Pass-through of non-HTTP Events has been added in v0.5.0.
36+
The destination path is /events by default.
37+
If you want to change the forwarding path or stop forwarding, please refer to the following sample for configuration.
38+
39+
```go
40+
func main() {
41+
log.Fatalln(adaptor.ListenAndServeWithOptions(
42+
"",
43+
handler,
44+
// aws.WithNonHTTPEventPath("/api"),
45+
// aws.WithoutNonHTTPEventPassThrough(),
46+
)
47+
}
48+
```
49+
3350
## Features
3451
- AWS Lambda support
3552
- [x] API Gateway REST API integration
@@ -45,6 +62,7 @@ func echoReplyHandler(w http.ResponseWriter, r *http.Request) {
4562
- [x] Get raw request value from Context
4663
- [x] Lambda container image function
4764
- [x] API Gateway Websocket API integration (Experimental)
65+
- [x] Non-HTTP event pass-through
4866
- AWS API Gateway utilities
4967
- [x] Strip stage var middleware
5068
- [ ] Abstract interface of RequestContext

aws/lambda.go

Lines changed: 83 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"net/http"
1414
)
1515

16+
const DefaultNonHTTPEventPath = "/events"
17+
1618
type LambdaHandlerOption func(handler *LambdaHandler)
1719

1820
type SDKSessionProvider func() (*session.Session, error)
@@ -31,14 +33,27 @@ func WithAWSConfigProvider(sp SDKConfigProvider) LambdaHandlerOption {
3133
}
3234
}
3335

36+
func WithNonHTTPEventPath(path string) LambdaHandlerOption {
37+
return func(handler *LambdaHandler) {
38+
handler.nonHTTPEventPath = path
39+
}
40+
}
41+
42+
func WithoutNonHTTPEventPassThrough() LambdaHandlerOption {
43+
return func(handler *LambdaHandler) {
44+
handler.nonHTTPEventPath = ""
45+
}
46+
}
47+
3448
type LambdaHandler struct {
35-
httpHandler http.Handler
36-
sessProv SDKSessionProvider
37-
sess *session.Session
38-
confProv SDKConfigProvider
39-
conf *aws.Config
40-
apiGW APIGatewayManagementAPI
41-
wsPathPrefix string
49+
httpHandler http.Handler
50+
sessProv SDKSessionProvider
51+
sess *session.Session
52+
confProv SDKConfigProvider
53+
conf *aws.Config
54+
apiGW APIGatewayManagementAPI
55+
wsPathPrefix string
56+
nonHTTPEventPath string
4257
}
4358

4459
func NewLambdaHandlerWithOption(h http.Handler, options []interface{}) lambda.Handler {
@@ -47,7 +62,8 @@ func NewLambdaHandlerWithOption(h http.Handler, options []interface{}) lambda.Ha
4762
confProv: func(ctx context.Context) (aws.Config, error) {
4863
return config.LoadDefaultConfig(ctx)
4964
},
50-
wsPathPrefix: DefaultWebsocketPathPrefix,
65+
wsPathPrefix: DefaultWebsocketPathPrefix,
66+
nonHTTPEventPath: DefaultNonHTTPEventPath,
5167
}
5268

5369
for _, opt := range options {
@@ -96,6 +112,19 @@ func (l *LambdaHandler) InvokeALBTargetGroup(ctx context.Context, request *event
96112
return ALBTargetResponse(w, multiValue)
97113
}
98114

115+
func (l *LambdaHandler) HandleNonHTTPEvent(ctx context.Context, event []byte, contentType string) ([]byte, error) {
116+
if l.nonHTTPEventPath == "" {
117+
return nil, fmt.Errorf("unknown lambda integration type and non-http event path is not set")
118+
}
119+
req, err := NewLambdaPassthroughRequest(ctx, event, l.nonHTTPEventPath, contentType)
120+
if err != nil {
121+
return nil, err
122+
}
123+
w := NewResponseWriter()
124+
l.httpHandler.ServeHTTP(w, req)
125+
return w.buf.Bytes(), nil
126+
}
127+
99128
func (l *LambdaHandler) ProvideAPIGatewayClient(ctx context.Context, request *events.APIGatewayWebsocketProxyRequest) (client APIGatewayManagementAPI, err error) {
100129
if l.apiGW != nil {
101130
return l.apiGW, nil
@@ -158,46 +187,46 @@ func (l *LambdaHandler) Invoke(ctx context.Context, payload []byte) ([]byte, err
158187
err error
159188
)
160189

161-
if err := json.Unmarshal(payload, &checker); err != nil {
162-
return nil, err
163-
}
164-
165-
switch checker.IntegrationType() {
166-
case APIGatewayRESTIntegration:
167-
event := &events.APIGatewayProxyRequest{}
168-
if err := json.Unmarshal(payload, event); err != nil {
169-
return nil, err
170-
}
171-
if trace.RequestEvent != nil {
172-
trace.RequestEvent(ctx, payload)
173-
}
174-
res, err = l.InvokeRESTAPI(ctx, event)
175-
case APIGatewayHTTPIntegration:
176-
event := &events.APIGatewayV2HTTPRequest{}
177-
if err := json.Unmarshal(payload, event); err != nil {
178-
return nil, err
179-
}
180-
if trace.RequestEvent != nil {
181-
trace.RequestEvent(ctx, payload)
182-
}
183-
res, err = l.InvokeHTTPAPI(ctx, event)
184-
case ALBTargetGroupIntegration:
185-
event := &events.ALBTargetGroupRequest{}
186-
if err := json.Unmarshal(payload, event); err != nil {
187-
return nil, err
188-
}
189-
if trace.RequestEvent != nil {
190-
trace.RequestEvent(ctx, payload)
191-
}
192-
res, err = l.InvokeALBTargetGroup(ctx, event)
193-
case APIGatewayWebsocketIntegration:
194-
event := &events.APIGatewayWebsocketProxyRequest{}
195-
if err := json.Unmarshal(payload, event); err != nil {
196-
return nil, err
190+
if err = json.Unmarshal(payload, &checker); err != nil {
191+
res, err = l.HandleNonHTTPEvent(ctx, payload, http.DetectContentType(payload))
192+
} else {
193+
switch checker.IntegrationType() {
194+
case APIGatewayRESTIntegration:
195+
event := &events.APIGatewayProxyRequest{}
196+
if err := json.Unmarshal(payload, event); err != nil {
197+
return nil, err
198+
}
199+
if trace.RequestEvent != nil {
200+
trace.RequestEvent(ctx, payload)
201+
}
202+
res, err = l.InvokeRESTAPI(ctx, event)
203+
case APIGatewayHTTPIntegration:
204+
event := &events.APIGatewayV2HTTPRequest{}
205+
if err := json.Unmarshal(payload, event); err != nil {
206+
return nil, err
207+
}
208+
if trace.RequestEvent != nil {
209+
trace.RequestEvent(ctx, payload)
210+
}
211+
res, err = l.InvokeHTTPAPI(ctx, event)
212+
case ALBTargetGroupIntegration:
213+
event := &events.ALBTargetGroupRequest{}
214+
if err := json.Unmarshal(payload, event); err != nil {
215+
return nil, err
216+
}
217+
if trace.RequestEvent != nil {
218+
trace.RequestEvent(ctx, payload)
219+
}
220+
res, err = l.InvokeALBTargetGroup(ctx, event)
221+
case APIGatewayWebsocketIntegration:
222+
event := &events.APIGatewayWebsocketProxyRequest{}
223+
if err := json.Unmarshal(payload, event); err != nil {
224+
return nil, err
225+
}
226+
res, err = l.InvokeWebsocketAPI(ctx, event)
227+
default:
228+
res, err = l.HandleNonHTTPEvent(ctx, payload, "application/json")
197229
}
198-
res, err = l.InvokeWebsocketAPI(ctx, event)
199-
default:
200-
return nil, fmt.Errorf("unknown lambda integration type")
201230
}
202231

203232
if err != nil {
@@ -208,9 +237,13 @@ func (l *LambdaHandler) Invoke(ctx context.Context, payload []byte) ([]byte, err
208237
trace.ResponseEvent(ctx, res)
209238
}
210239

211-
if responseBytes, err := json.Marshal(res); err != nil {
212-
return nil, err
240+
if b, ok := res.([]byte); ok {
241+
return b, nil
213242
} else {
214-
return responseBytes, nil
243+
if responseBytes, err := json.Marshal(res); err != nil {
244+
return nil, err
245+
} else {
246+
return responseBytes, nil
247+
}
215248
}
216249
}

aws/lambda_passthrough.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
Package aws provides an implementation using aws-sdk-go.
3+
4+
Lambda event type compatibility layer for AWS API Gateway with HTTP API mode.
5+
6+
See lambda event detail:
7+
https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
8+
*/
9+
package aws
10+
11+
import (
12+
"bytes"
13+
"context"
14+
"fmt"
15+
"github.com/yacchi/lambda-http-adaptor/types"
16+
"net/http"
17+
"strconv"
18+
"strings"
19+
)
20+
21+
// NewLambdaPassthroughRequest Raw lambda event type to http.Request converter.
22+
func NewLambdaPassthroughRequest(ctx context.Context, payload []byte, eventPath string, contentType string) (r *http.Request, err error) {
23+
var (
24+
body *bytes.Buffer
25+
header = make(http.Header)
26+
)
27+
28+
// build raw request URL
29+
rawURL := "http://localhost/" + strings.TrimLeft(eventPath, "/")
30+
body = bytes.NewBuffer(payload)
31+
32+
r, err = http.NewRequestWithContext(ctx, "POST", rawURL, body)
33+
if err != nil {
34+
return nil, fmt.Errorf("lambda_passthrough: new request: %w", err)
35+
}
36+
37+
r.Header = header
38+
r.Header.Set(types.HTTPHeaderContentType, contentType)
39+
r.Header.Set(types.HTTPHeaderContentLength, strconv.Itoa(body.Len()))
40+
r.RemoteAddr = "127.0.0.1"
41+
r.RequestURI = r.URL.RequestURI()
42+
r.Host = r.URL.Host
43+
44+
return
45+
}

example/simple/api/api.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ func (w *writerWrapper) WriteHeader(code int) {
3535
func ProvideAPI() http.Handler {
3636
mux := http.NewServeMux()
3737

38+
mux.HandleFunc("/events", func(writer http.ResponseWriter, request *http.Request) {
39+
writer.WriteHeader(http.StatusOK)
40+
body, _ := io.ReadAll(request.Body)
41+
println(request.Header.Get("Content-Type"))
42+
println(string(body))
43+
_, err := writer.Write(body)
44+
if err != nil {
45+
fmt.Println(err)
46+
}
47+
})
48+
3849
mux.HandleFunc("/websocket/$connect", func(writer http.ResponseWriter, request *http.Request) {
3950
writer.WriteHeader(http.StatusOK)
4051
err := json.NewEncoder(writer).Encode(map[string]interface{}{

0 commit comments

Comments
 (0)