-
Notifications
You must be signed in to change notification settings - Fork 15
/
handlers.go
146 lines (127 loc) · 3.56 KB
/
handlers.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
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"net/url"
"strings"
)
type preseedResponse struct {
Request struct {
Body string
URL string
Method string
}
Response struct {
Body string
StatusCode int
Headers map[string]string
}
}
// PreseedHandler preseeds a Cacher, according to a Hasher
func PreseedHandler(cacher Cacher, hasher Hasher) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
dec := json.NewDecoder(r.Body)
var preseedResp preseedResponse
err := dec.Decode(&preseedResp)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
fakeReq, err := http.NewRequest(
preseedResp.Request.Method,
preseedResp.Request.URL,
strings.NewReader(preseedResp.Request.Body),
)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
hash := hasher.Hash(fakeReq)
response := cacher.Get(hash)
w.Header().Add("chameleon-request-hash", hash)
if response != nil {
log.Printf("-> Proxying [preseeding;cached: %v] to %v\n", hash, preseedResp.Request.URL)
w.WriteHeader(200)
return
}
log.Printf("-> Proxying [preseeding;not cached: %v] to %v\n", hash, preseedResp.Request.URL)
rec := httptest.NewRecorder()
rec.Body = bytes.NewBufferString(preseedResp.Response.Body)
rec.Code = preseedResp.Response.StatusCode
for name, value := range preseedResp.Response.Headers {
rec.Header().Set(name, value)
}
// Signal to the cacher to skip the disk
rec.Header().Set("_chameleon-seeded-skip-disk", "true")
// Don't need the response
_ = cacher.Put(hash, rec)
w.WriteHeader(201)
}
}
// CachedProxyHandler proxies a given URL and stores/fetches content from a Cacher, according to a Hasher
func CachedProxyHandler(serverURL *url.URL, cacher Cacher, hasher Hasher) http.HandlerFunc {
parsedURL, err := url.Parse(serverURL.String())
if err != nil {
panic(err)
}
return func(w http.ResponseWriter, r *http.Request) {
// Change the host for the request for this configuration
r.Host = parsedURL.Host
r.URL.Host = r.Host
r.URL.Scheme = parsedURL.Scheme
r.RequestURI = ""
hash := r.Header.Get("chameleon-request-hash")
if hash == "" {
hash = hasher.Hash(r)
}
response := cacher.Get(hash)
if response != nil {
log.Printf("-> Proxying [cached: %v] to %v\n", hash, r.URL)
} else {
// We don't have a cached response yet
log.Printf("-> Proxying [not cached: %v] to %v\n", hash, r.URL)
// Create a recorder, so we can get data out and modify it (if needed)
rec := httptest.NewRecorder()
ProxyHandler(rec, r) // Actually call our handler
response = cacher.Put(hash, rec)
}
for k, v := range response.Headers {
w.Header().Add(k, v)
}
w.Header().Add("chameleon-request-hash", hash)
w.WriteHeader(response.StatusCode)
// If this fails, there isn't much to do
_, _ = io.Copy(w, bytes.NewReader(response.Body))
}
}
func copyHeaders(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
// ProxyHandler implements a standard HTTP handler to proxy a given request and returns the response
func ProxyHandler(w http.ResponseWriter, r *http.Request) {
client := &http.Client{}
resp, err := client.Do(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer func() {
// If this fails, there isn't much to do
_ = resp.Body.Close()
}()
copyHeaders(w.Header(), resp.Header)
w.WriteHeader(resp.StatusCode)
// If this fails, there isn't much to do
_, _ = io.Copy(w, resp.Body) // Proxy through
}