Skip to content

Commit fc8c062

Browse files
authored
Support for 2.0 payload format (awslabs#80)
* handle v2 payload format * populate path if rawpath is empty
1 parent 74d7778 commit fc8c062

File tree

7 files changed

+968
-0
lines changed

7 files changed

+968
-0
lines changed

core/requestv2.go

+223
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
// Package core provides utility methods that help convert proxy events
2+
// into an http.Request and http.ResponseWriter
3+
package core
4+
5+
import (
6+
"bytes"
7+
"context"
8+
"encoding/base64"
9+
"encoding/json"
10+
"errors"
11+
"fmt"
12+
"log"
13+
"net/http"
14+
"net/url"
15+
"os"
16+
"strings"
17+
18+
"github.com/aws/aws-lambda-go/events"
19+
"github.com/aws/aws-lambda-go/lambdacontext"
20+
)
21+
22+
// RequestAccessorV2 objects give access to custom API Gateway properties
23+
// in the request.
24+
type RequestAccessorV2 struct {
25+
stripBasePath string
26+
}
27+
28+
// GetAPIGatewayContextV2 extracts the API Gateway context object from a
29+
// request's custom header.
30+
// Returns a populated events.APIGatewayProxyRequestContext object from
31+
// the request.
32+
func (r *RequestAccessorV2) GetAPIGatewayContextV2(req *http.Request) (events.APIGatewayV2HTTPRequestContext, error) {
33+
if req.Header.Get(APIGwContextHeader) == "" {
34+
return events.APIGatewayV2HTTPRequestContext{}, errors.New("No context header in request")
35+
}
36+
context := events.APIGatewayV2HTTPRequestContext{}
37+
err := json.Unmarshal([]byte(req.Header.Get(APIGwContextHeader)), &context)
38+
if err != nil {
39+
log.Println("Erorr while unmarshalling context")
40+
log.Println(err)
41+
return events.APIGatewayV2HTTPRequestContext{}, err
42+
}
43+
return context, nil
44+
}
45+
46+
// GetAPIGatewayStageVars extracts the API Gateway stage variables from a
47+
// request's custom header.
48+
// Returns a map[string]string of the stage variables and their values from
49+
// the request.
50+
func (r *RequestAccessorV2) GetAPIGatewayStageVars(req *http.Request) (map[string]string, error) {
51+
stageVars := make(map[string]string)
52+
if req.Header.Get(APIGwStageVarsHeader) == "" {
53+
return stageVars, errors.New("No stage vars header in request")
54+
}
55+
err := json.Unmarshal([]byte(req.Header.Get(APIGwStageVarsHeader)), &stageVars)
56+
if err != nil {
57+
log.Println("Erorr while unmarshalling stage variables")
58+
log.Println(err)
59+
return stageVars, err
60+
}
61+
return stageVars, nil
62+
}
63+
64+
// StripBasePath instructs the RequestAccessor object that the given base
65+
// path should be removed from the request path before sending it to the
66+
// framework for routing. This is used when API Gateway is configured with
67+
// base path mappings in custom domain names.
68+
func (r *RequestAccessorV2) StripBasePath(basePath string) string {
69+
if strings.Trim(basePath, " ") == "" {
70+
r.stripBasePath = ""
71+
return ""
72+
}
73+
74+
newBasePath := basePath
75+
if !strings.HasPrefix(newBasePath, "/") {
76+
newBasePath = "/" + newBasePath
77+
}
78+
79+
if strings.HasSuffix(newBasePath, "/") {
80+
newBasePath = newBasePath[:len(newBasePath)-1]
81+
}
82+
83+
r.stripBasePath = newBasePath
84+
85+
return newBasePath
86+
}
87+
88+
// ProxyEventToHTTPRequest converts an API Gateway proxy event into a http.Request object.
89+
// Returns the populated http request with additional two custom headers for the stage variables and API Gateway context.
90+
// To access these properties use the GetAPIGatewayStageVars and GetAPIGatewayContext method of the RequestAccessor object.
91+
func (r *RequestAccessorV2) ProxyEventToHTTPRequest(req events.APIGatewayV2HTTPRequest) (*http.Request, error) {
92+
httpRequest, err := r.EventToRequest(req)
93+
if err != nil {
94+
log.Println(err)
95+
return nil, err
96+
}
97+
return addToHeaderV2(httpRequest, req)
98+
}
99+
100+
// EventToRequestWithContext converts an API Gateway proxy event and context into an http.Request object.
101+
// Returns the populated http request with lambda context, stage variables and APIGatewayProxyRequestContext as part of its context.
102+
// Access those using GetAPIGatewayContextFromContext, GetStageVarsFromContext and GetRuntimeContextFromContext functions in this package.
103+
func (r *RequestAccessorV2) EventToRequestWithContext(ctx context.Context, req events.APIGatewayV2HTTPRequest) (*http.Request, error) {
104+
httpRequest, err := r.EventToRequest(req)
105+
if err != nil {
106+
log.Println(err)
107+
return nil, err
108+
}
109+
return addToContextV2(ctx, httpRequest, req), nil
110+
}
111+
112+
// EventToRequest converts an API Gateway proxy event into an http.Request object.
113+
// Returns the populated request maintaining headers
114+
func (r *RequestAccessorV2) EventToRequest(req events.APIGatewayV2HTTPRequest) (*http.Request, error) {
115+
decodedBody := []byte(req.Body)
116+
if req.IsBase64Encoded {
117+
base64Body, err := base64.StdEncoding.DecodeString(req.Body)
118+
if err != nil {
119+
return nil, err
120+
}
121+
decodedBody = base64Body
122+
}
123+
124+
path := req.RawPath
125+
126+
//if RawPath empty is, populate from request context
127+
if len(path) == 0 {
128+
path = req.RequestContext.HTTP.Path
129+
}
130+
131+
if r.stripBasePath != "" && len(r.stripBasePath) > 1 {
132+
if strings.HasPrefix(path, r.stripBasePath) {
133+
path = strings.Replace(path, r.stripBasePath, "", 1)
134+
}
135+
}
136+
if !strings.HasPrefix(path, "/") {
137+
path = "/" + path
138+
}
139+
serverAddress := "https://" + req.RequestContext.DomainName
140+
if customAddress, ok := os.LookupEnv(CustomHostVariable); ok {
141+
serverAddress = customAddress
142+
}
143+
path = serverAddress + path
144+
145+
if len(req.RawQueryString) > 0 {
146+
path += "?" + req.RawQueryString
147+
} else if len(req.QueryStringParameters) > 0 {
148+
values := url.Values{}
149+
for key, value := range req.QueryStringParameters {
150+
values.Add(key, value)
151+
}
152+
path += "?" + values.Encode()
153+
}
154+
155+
httpRequest, err := http.NewRequest(
156+
strings.ToUpper(req.RequestContext.HTTP.Method),
157+
path,
158+
bytes.NewReader(decodedBody),
159+
)
160+
161+
if err != nil {
162+
fmt.Printf("Could not convert request %s:%s to http.Request\n", req.RequestContext.HTTP.Method, req.RequestContext.HTTP.Path)
163+
log.Println(err)
164+
return nil, err
165+
}
166+
167+
for headerKey, headerValue := range req.Headers {
168+
for _, val := range strings.Split(headerValue, ",") {
169+
httpRequest.Header.Add(headerKey, strings.Trim(val, " "))
170+
}
171+
}
172+
173+
httpRequest.RequestURI = httpRequest.URL.RequestURI()
174+
175+
return httpRequest, nil
176+
}
177+
178+
func addToHeaderV2(req *http.Request, apiGwRequest events.APIGatewayV2HTTPRequest) (*http.Request, error) {
179+
stageVars, err := json.Marshal(apiGwRequest.StageVariables)
180+
if err != nil {
181+
log.Println("Could not marshal stage variables for custom header")
182+
return nil, err
183+
}
184+
req.Header.Add(APIGwStageVarsHeader, string(stageVars))
185+
apiGwContext, err := json.Marshal(apiGwRequest.RequestContext)
186+
if err != nil {
187+
log.Println("Could not Marshal API GW context for custom header")
188+
return req, err
189+
}
190+
req.Header.Add(APIGwContextHeader, string(apiGwContext))
191+
return req, nil
192+
}
193+
194+
func addToContextV2(ctx context.Context, req *http.Request, apiGwRequest events.APIGatewayV2HTTPRequest) *http.Request {
195+
lc, _ := lambdacontext.FromContext(ctx)
196+
rc := requestContextV2{lambdaContext: lc, gatewayProxyContext: apiGwRequest.RequestContext, stageVars: apiGwRequest.StageVariables}
197+
ctx = context.WithValue(ctx, ctxKey{}, rc)
198+
return req.WithContext(ctx)
199+
}
200+
201+
// GetAPIGatewayV2ContextFromContext retrieve APIGatewayProxyRequestContext from context.Context
202+
func GetAPIGatewayV2ContextFromContext(ctx context.Context) (events.APIGatewayV2HTTPRequestContext, bool) {
203+
v, ok := ctx.Value(ctxKey{}).(requestContextV2)
204+
return v.gatewayProxyContext, ok
205+
}
206+
207+
// GetRuntimeContextFromContextV2 retrieve Lambda Runtime Context from context.Context
208+
func GetRuntimeContextFromContextV2(ctx context.Context) (*lambdacontext.LambdaContext, bool) {
209+
v, ok := ctx.Value(ctxKey{}).(requestContextV2)
210+
return v.lambdaContext, ok
211+
}
212+
213+
// GetStageVarsFromContextV2 retrieve stage variables from context
214+
func GetStageVarsFromContextV2(ctx context.Context) (map[string]string, bool) {
215+
v, ok := ctx.Value(ctxKey{}).(requestContextV2)
216+
return v.stageVars, ok
217+
}
218+
219+
type requestContextV2 struct {
220+
lambdaContext *lambdacontext.LambdaContext
221+
gatewayProxyContext events.APIGatewayV2HTTPRequestContext
222+
stageVars map[string]string
223+
}

0 commit comments

Comments
 (0)