This repository has been archived by the owner on Sep 14, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
request.go
155 lines (132 loc) · 4.02 KB
/
request.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
package apigo
import (
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/aws/aws-lambda-go/events"
"github.com/pkg/errors"
)
// Request is an wrapper which helps transforming event from AWS API
// Gateway as a http.Request.
type Request struct {
Context context.Context
Event events.APIGatewayProxyRequest
Path string
Body *bytes.Reader
}
// NewRequest defines new RequestBuilder with context and event data
// provided from the API Gateway.
func NewRequest(ctx context.Context, ev events.APIGatewayProxyRequest) *Request {
return &Request{
Context: ctx,
Event: ev,
}
}
// StripBasePath removes a BasePath from the Path fragment of the URL.
// StripBasePath must be run before RequestBuilder.ParseURL function.
func (r *Request) StripBasePath(basePath string) {
r.Path = omitBasePath(r.Event.Path, basePath)
}
// omitBasePath strips out the base path from the given path.
//
// It allows to support both API endpoints (default, auto-generated
// "execute-api" address and configured Base Path Mapping/ with a Custom Domain
// Name), while preserving the same routing registered on the http.Handler.
func omitBasePath(path string, basePath string) string {
if path == "/" || basePath == "" {
return path
}
if strings.HasPrefix(path, "/"+basePath) {
path = strings.Replace(path, basePath, "", 1)
}
if strings.HasPrefix(path, "//") {
path = path[1:]
}
return path
}
// CreateRequest provides *http.Request to the RequestBuilder.
func (r *Request) CreateRequest(host string) (*http.Request, error) {
if err := r.ParseBody(); err != nil {
return nil, err
}
uri := r.ParseURL(host).String()
req, err := http.NewRequest(r.Event.HTTPMethod, uri, r.Body)
if err != nil {
return nil, err
}
req.TLS = &tls.ConnectionState{}
req.RequestURI = uri
return req, nil
}
// ParseURL provides URL (as a *url.URL) to the RequestBuilder.
func (r *Request) ParseURL(host string) *url.URL {
// Whether path has been already defined (i.e. processed by previous
// function) then use it, otherwise use path from the event.
path := r.Path
if len(path) == 0 {
path = r.Event.Path
}
// Parse URL to *url.URL
u := &url.URL{
Scheme: r.Event.Headers["X-Forwarded-Proto"],
Host: host,
Path: path,
}
// Query-string
q := url.Values(r.Event.MultiValueQueryStringParameters)
u.RawQuery = q.Encode()
return u
}
// ParseBody provides body of the request to the RequestBuilder.
func (r *Request) ParseBody() error {
body := []byte(r.Event.Body)
if r.Event.IsBase64Encoded {
b, err := base64.StdEncoding.DecodeString(r.Event.Body)
if err != nil {
return errors.Wrap(err, "decoding base64 body")
}
body = b
}
r.Body = bytes.NewReader(body)
return nil
}
// AttachContext attaches events' RequestContext to the http.Request.
func (r *Request) AttachContext(req *http.Request) {
*req = *req.WithContext(NewContext(r.Context, r.Event))
}
// SetRemoteAddr sets RemoteAddr to the request.
func (r *Request) SetRemoteAddr(req *http.Request) {
req.RemoteAddr = r.Event.RequestContext.Identity.SourceIP
}
// SetHeaderFields sets headers to the request.
func (r *Request) SetHeaderFields(req *http.Request) {
for k, hs := range r.Event.MultiValueHeaders {
for _, v := range hs {
req.Header.Add(k, v)
}
}
}
// SetContentLength sets Content-Length to the request if it has not been set.
func (r *Request) SetContentLength(req *http.Request) {
if req.Header.Get("Content-Length") == "" {
req.Header.Set("Content-Length", strconv.Itoa(r.Body.Len()))
}
}
// SetCustomHeaders assigns X-Request-Id and X-Stage from the event's
// Request Context.
func (r *Request) SetCustomHeaders(req *http.Request) {
req.Header.Set("X-Request-Id", r.Event.RequestContext.RequestID)
req.Header.Set("X-Stage", r.Event.RequestContext.Stage)
}
// SetXRayHeader sets AWS X-Ray Trace ID from the event's context.
func (r *Request) SetXRayHeader(req *http.Request) {
if traceID := r.Context.Value("x-amzn-trace-id"); traceID != nil {
req.Header.Set("X-Amzn-Trace-Id", fmt.Sprintf("%v", traceID))
}
}