forked from luraproject/lura
/
endpoint.go
166 lines (137 loc) · 4.9 KB
/
endpoint.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
package mux
import (
"context"
"fmt"
"net/http"
"net/textproto"
"regexp"
"strings"
"github.com/devopsfaith/krakend/config"
"github.com/devopsfaith/krakend/core"
"github.com/devopsfaith/krakend/proxy"
"github.com/devopsfaith/krakend/router"
)
const requestParamsAsterisk string = "*"
// HandlerFactory creates a handler function that adapts the mux router with the injected proxy
type HandlerFactory func(*config.EndpointConfig, proxy.Proxy) http.HandlerFunc
// EndpointHandler is a HandlerFactory that adapts the mux router with the injected proxy
// and the default RequestBuilder
var EndpointHandler = CustomEndpointHandler(NewRequest)
// CustomEndpointHandler returns a HandlerFactory with the received RequestBuilder using the default ToHTTPError function
func CustomEndpointHandler(rb RequestBuilder) HandlerFactory {
return CustomEndpointHandlerWithHTTPError(rb, router.DefaultToHTTPError)
}
// CustomEndpointHandlerWithHTTPError returns a HandlerFactory with the received RequestBuilder
func CustomEndpointHandlerWithHTTPError(rb RequestBuilder, errF router.ToHTTPError) HandlerFactory {
return func(configuration *config.EndpointConfig, prxy proxy.Proxy) http.HandlerFunc {
cacheControlHeaderValue := fmt.Sprintf("public, max-age=%d", int(configuration.CacheTTL.Seconds()))
isCacheEnabled := configuration.CacheTTL.Seconds() != 0
render := getRender(configuration)
headersToSend := configuration.HeadersToPass
if len(headersToSend) == 0 {
headersToSend = router.HeadersToSend
}
method := strings.ToTitle(configuration.Method)
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(core.KrakendHeaderName, core.KrakendHeaderValue)
if r.Method != method {
w.Header().Set(router.CompleteResponseHeaderName, router.HeaderIncompleteResponseValue)
http.Error(w, "", http.StatusMethodNotAllowed)
return
}
requestCtx, cancel := context.WithTimeout(r.Context(), configuration.Timeout)
response, err := prxy(requestCtx, rb(r, configuration.QueryString, headersToSend))
select {
case <-requestCtx.Done():
if err == nil {
err = router.ErrInternalError
}
default:
}
if response != nil && len(response.Data) > 0 {
if response.IsComplete {
w.Header().Set(router.CompleteResponseHeaderName, router.HeaderCompleteResponseValue)
if isCacheEnabled {
w.Header().Set("Cache-Control", cacheControlHeaderValue)
}
} else {
w.Header().Set(router.CompleteResponseHeaderName, router.HeaderIncompleteResponseValue)
}
for k, vs := range response.Metadata.Headers {
for _, v := range vs {
w.Header().Add(k, v)
}
}
} else {
w.Header().Set(router.CompleteResponseHeaderName, router.HeaderIncompleteResponseValue)
if err != nil {
if t, ok := err.(responseError); ok {
http.Error(w, err.Error(), t.StatusCode())
} else {
http.Error(w, err.Error(), errF(err))
}
cancel()
return
}
}
render(w, response)
cancel()
}
}
}
// RequestBuilder is a function that creates a proxy.Request from the received http request
type RequestBuilder func(r *http.Request, queryString, headersToSend []string) *proxy.Request
// ParamExtractor is a function that extracts query params from the requested uri
type ParamExtractor func(r *http.Request) map[string]string
// NewRequest is a RequestBuilder that creates a proxy request from the received http request without
// processing the uri params
var NewRequest = NewRequestBuilder(func(_ *http.Request) map[string]string {
return map[string]string{}
})
// NewRequestBuilder gets a RequestBuilder with the received ParamExtractor as a query param
// extraction mechanism
func NewRequestBuilder(paramExtractor ParamExtractor) RequestBuilder {
var re = regexp.MustCompile(`^\[?([\d.:]+)\]?(:[\d]*)$`)
return func(r *http.Request, queryString, headersToSend []string) *proxy.Request {
params := paramExtractor(r)
headers := make(map[string][]string, 2+len(headersToSend))
for _, k := range headersToSend {
if k == requestParamsAsterisk {
headers = r.Header
break
}
if h, ok := r.Header[textproto.CanonicalMIMEHeaderKey(k)]; ok {
headers[k] = h
}
}
matches := re.FindAllStringSubmatch(r.RemoteAddr, -1)
if len(matches) > 0 && len(matches[0]) > 1 {
headers["X-Forwarded-For"] = []string{matches[0][1]}
} else {
headers["X-Forwarded-For"] = []string{r.RemoteAddr}
}
headers["User-Agent"] = router.UserAgentHeaderValue
query := make(map[string][]string, len(queryString))
queryValues := r.URL.Query()
for i := range queryString {
if queryString[i] == requestParamsAsterisk {
query = queryValues
break
}
if v, ok := queryValues[queryString[i]]; ok && len(v) > 0 {
query[queryString[i]] = v
}
}
return &proxy.Request{
Method: r.Method,
Query: query,
Body: r.Body,
Params: params,
Headers: headers,
}
}
}
type responseError interface {
error
StatusCode() int
}