Skip to content

Commit bf16974

Browse files
authored
Merge pull request #159 from arentta/master
Add code to convert ALB events to http.Request and http.ResponseWriter
2 parents fb2efb1 + 10d7bab commit bf16974

15 files changed

+1252
-0
lines changed

core/requestALB.go

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// Package core provides utility methods that help convert ALB 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+
"strings"
16+
17+
"github.com/aws/aws-lambda-go/events"
18+
"github.com/aws/aws-lambda-go/lambdacontext"
19+
)
20+
21+
const (
22+
// ALBContextHeader is the custom header key used to store the
23+
// ALB ELB context. To access the Context properties use the
24+
// GetALBContext method of the RequestAccessorALB object.
25+
ALBContextHeader = "X-GoLambdaProxy-ALB-Context"
26+
)
27+
28+
// RequestAccessorALB objects give access to custom ALB Target Group properties
29+
// in the request.
30+
type RequestAccessorALB struct {
31+
stripBasePath string
32+
}
33+
34+
// GetALBContext extracts the ALB context object from a request's custom header.
35+
// Returns a populated events.ALBTargetGroupRequestContext object from the request.
36+
func (r *RequestAccessorALB) GetContextALB(req *http.Request) (events.ALBTargetGroupRequestContext, error) {
37+
if req.Header.Get(ALBContextHeader) == "" {
38+
return events.ALBTargetGroupRequestContext{}, errors.New("no context header in request")
39+
}
40+
context := events.ALBTargetGroupRequestContext{}
41+
err := json.Unmarshal([]byte(req.Header.Get(ALBContextHeader)), &context)
42+
if err != nil {
43+
log.Println("Error while unmarshalling context")
44+
log.Println(err)
45+
return events.ALBTargetGroupRequestContext{}, err
46+
}
47+
return context, nil
48+
}
49+
50+
// StripBasePath instructs the RequestAccessor object that the given base
51+
// path should be removed from the request path before sending it to the
52+
// framework for routing. This is used when API Gateway is configured with
53+
// base path mappings in custom domain names.
54+
func (r *RequestAccessorALB) StripBasePath(basePath string) string {
55+
if strings.Trim(basePath, " ") == "" {
56+
r.stripBasePath = ""
57+
return ""
58+
}
59+
60+
newBasePath := basePath
61+
if !strings.HasPrefix(newBasePath, "/") {
62+
newBasePath = "/" + newBasePath
63+
}
64+
65+
if strings.HasSuffix(newBasePath, "/") {
66+
newBasePath = newBasePath[:len(newBasePath)-1]
67+
}
68+
69+
r.stripBasePath = newBasePath
70+
71+
return newBasePath
72+
}
73+
74+
// ProxyEventToHTTPRequest converts an ALB Target Group Request event into a http.Request object.
75+
// Returns the populated http request with additional custom header for the ALB context.
76+
// To access these properties use the GetALBContext method of the RequestAccessorALB object.
77+
func (r *RequestAccessorALB) ProxyEventToHTTPRequest(req events.ALBTargetGroupRequest) (*http.Request, error) {
78+
httpRequest, err := r.EventToRequest(req)
79+
if err != nil {
80+
log.Println(err)
81+
return nil, err
82+
}
83+
return addToHeaderALB(httpRequest, req)
84+
}
85+
86+
// EventToRequestWithContext converts an ALB Target Group Request event and context into an http.Request object.
87+
// Returns the populated http request with lambda context, ALB TargetGroup RequestContext as part of its context.
88+
func (r *RequestAccessorALB) EventToRequestWithContext(ctx context.Context, req events.ALBTargetGroupRequest) (*http.Request, error) {
89+
httpRequest, err := r.EventToRequest(req)
90+
if err != nil {
91+
log.Println(err)
92+
return nil, err
93+
}
94+
return addToContextALB(ctx, httpRequest, req), nil
95+
}
96+
97+
// EventToRequest converts an ALB TargetGroup event into an http.Request object.
98+
// Returns the populated request maintaining headers
99+
func (r *RequestAccessorALB) EventToRequest(req events.ALBTargetGroupRequest) (*http.Request, error) {
100+
decodedBody := []byte(req.Body)
101+
if req.IsBase64Encoded {
102+
base64Body, err := base64.StdEncoding.DecodeString(req.Body)
103+
if err != nil {
104+
return nil, err
105+
}
106+
decodedBody = base64Body
107+
}
108+
109+
path := req.Path
110+
if r.stripBasePath != "" && len(r.stripBasePath) > 1 {
111+
if strings.HasPrefix(path, r.stripBasePath) {
112+
path = strings.Replace(path, r.stripBasePath, "", 1)
113+
}
114+
}
115+
if !strings.HasPrefix(path, "/") {
116+
path = "/" + path
117+
}
118+
serverAddress := "https://" + req.Headers["host"]
119+
// if customAddress, ok := os.LookupEnv(CustomHostVariable); ok {
120+
// serverAddress = customAddress
121+
// }
122+
path = serverAddress + path
123+
124+
if len(req.MultiValueQueryStringParameters) > 0 {
125+
queryString := ""
126+
for q, l := range req.MultiValueQueryStringParameters {
127+
for _, v := range l {
128+
if queryString != "" {
129+
queryString += "&"
130+
}
131+
queryString += url.QueryEscape(q) + "=" + url.QueryEscape(v)
132+
}
133+
}
134+
path += "?" + queryString
135+
} else if len(req.QueryStringParameters) > 0 {
136+
// Support `QueryStringParameters` for backward compatibility.
137+
// https://github.com/awslabs/aws-lambda-go-api-proxy/issues/37
138+
queryString := ""
139+
for q := range req.QueryStringParameters {
140+
if queryString != "" {
141+
queryString += "&"
142+
}
143+
queryString += url.QueryEscape(q) + "=" + url.QueryEscape(req.QueryStringParameters[q])
144+
}
145+
path += "?" + queryString
146+
}
147+
148+
httpRequest, err := http.NewRequest(
149+
strings.ToUpper(req.HTTPMethod),
150+
path,
151+
bytes.NewReader(decodedBody),
152+
)
153+
154+
if err != nil {
155+
fmt.Printf("Could not convert request %s:%s to http.Request\n", req.HTTPMethod, req.Path)
156+
log.Println(err)
157+
return nil, err
158+
}
159+
160+
if req.MultiValueHeaders != nil {
161+
for k, values := range req.MultiValueHeaders {
162+
for _, value := range values {
163+
httpRequest.Header.Add(k, value)
164+
}
165+
}
166+
} else {
167+
for h := range req.Headers {
168+
httpRequest.Header.Add(h, req.Headers[h])
169+
}
170+
}
171+
172+
httpRequest.RequestURI = httpRequest.URL.RequestURI()
173+
174+
return httpRequest, nil
175+
}
176+
177+
func addToHeaderALB(req *http.Request, albRequest events.ALBTargetGroupRequest) (*http.Request, error) {
178+
albContext, err := json.Marshal(albRequest.RequestContext)
179+
if err != nil {
180+
log.Println("Could not Marshal ALB context for custom header")
181+
return req, err
182+
}
183+
req.Header.Set(ALBContextHeader, string(albContext))
184+
return req, nil
185+
}
186+
187+
// adds context data to http request so we can pass
188+
func addToContextALB(ctx context.Context, req *http.Request, albRequest events.ALBTargetGroupRequest) *http.Request {
189+
lc, _ := lambdacontext.FromContext(ctx)
190+
rc := requestContextALB{lambdaContext: lc, albContext: albRequest.RequestContext}
191+
ctx = context.WithValue(ctx, ctxKey{}, rc)
192+
return req.WithContext(ctx)
193+
}
194+
195+
// GetALBTargetGroupRequestFromContext retrieve ALBTargetGroupt from context.Context
196+
func GetTargetGroupRequetFromContextALB(ctx context.Context) (events.ALBTargetGroupRequestContext, bool) {
197+
v, ok := ctx.Value(ctxKey{}).(requestContextALB)
198+
return v.albContext, ok
199+
}
200+
201+
// GetRuntimeContextFromContext retrieve Lambda Runtime Context from context.Context
202+
func GetRuntimeContextFromContextALB(ctx context.Context) (*lambdacontext.LambdaContext, bool) {
203+
v, ok := ctx.Value(ctxKey{}).(requestContextALB)
204+
return v.lambdaContext, ok
205+
}
206+
207+
type requestContextALB struct {
208+
lambdaContext *lambdacontext.LambdaContext
209+
albContext events.ALBTargetGroupRequestContext
210+
}

0 commit comments

Comments
 (0)