/
headers.go
193 lines (166 loc) · 6.03 KB
/
headers.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package ipfsproxy
import (
"fmt"
"net/http"
"time"
"github.com/mtdepin/rep-mgr/version"
)
// This file has the collection of header-related functions
// We will extract all these from a pre-flight OPTIONs request to IPFS to
// use in the respose of a hijacked request (usually POST).
var corsHeaders = []string{
// These two must be returned as IPFS would return them
// for a request with the same origin.
"Access-Control-Allow-Origin",
"Vary", // seems more correctly set in OPTIONS than other requests.
// This is returned by OPTIONS so we can take it, even if ipfs sets
// it for nothing by default.
"Access-Control-Allow-Credentials",
// Unfortunately this one should not come with OPTIONS by default,
// but only with the real request itself.
// We use extractHeadersDefault for it, even though I think
// IPFS puts it in OPTIONS responses too. In any case, ipfs
// puts it on all requests as of 0.4.18, so it should be OK.
// "Access-Control-Expose-Headers",
// Only for preflight responses, we do not need
// these since we will simply proxy OPTIONS requests and not
// handle them.
//
// They are here for reference about other CORS related headers.
// "Access-Control-Max-Age",
// "Access-Control-Allow-Methods",
// "Access-Control-Allow-Headers",
}
// This can be used to hardcode header extraction from the proxy if we ever
// need to. It is appended to config.ExtractHeaderExtra.
// Maybe "X-Ipfs-Gateway" is a good candidate.
var extractHeadersDefault = []string{
"Access-Control-Expose-Headers",
}
const ipfsHeadersTimestampKey = "proxyHeadersTS"
// ipfsHeaders returns all the headers we want to extract-once from IPFS: a
// concatenation of extractHeadersDefault and config.ExtractHeadersExtra.
func (proxy *Server) ipfsHeaders() []string {
return append(extractHeadersDefault, proxy.config.ExtractHeadersExtra...)
}
// rememberIPFSHeaders extracts headers and stores them for re-use with
// setIPFSHeaders.
func (proxy *Server) rememberIPFSHeaders(hdrs http.Header) {
for _, h := range proxy.ipfsHeaders() {
proxy.ipfsHeadersStore.Store(h, hdrs[h])
}
// use the sync map to store the ts
proxy.ipfsHeadersStore.Store(ipfsHeadersTimestampKey, time.Now())
}
// returns whether we can consider that whatever headers we are
// storing have a valid TTL still.
func (proxy *Server) headersWithinTTL() bool {
ttl := proxy.config.ExtractHeadersTTL
if ttl == 0 {
return true
}
tsRaw, ok := proxy.ipfsHeadersStore.Load(ipfsHeadersTimestampKey)
if !ok {
return false
}
ts, ok := tsRaw.(time.Time)
if !ok {
return false
}
lifespan := time.Since(ts)
return lifespan < ttl
}
// setIPFSHeaders adds the known IPFS Headers to the destination
// and returns true if we could set all the headers in the list and
// the TTL has not expired.
// False is used to determine if we need to make a request to try
// to extract these headers.
func (proxy *Server) setIPFSHeaders(dest http.Header) bool {
r := true
if !proxy.headersWithinTTL() {
r = false
// still set those headers we can set in the destination.
// We do our best there, since maybe the ipfs daemon
// is down and what we have now is all we can use.
}
for _, h := range proxy.ipfsHeaders() {
v, ok := proxy.ipfsHeadersStore.Load(h)
if !ok {
r = false
continue
}
dest[h] = v.([]string)
}
return r
}
// copyHeadersFromIPFSWithRequest makes a request to IPFS as used by the proxy
// and copies the given list of hdrs from the response to the dest http.Header
// object.
func (proxy *Server) copyHeadersFromIPFSWithRequest(
hdrs []string,
dest http.Header, req *http.Request,
) error {
res, err := proxy.reverseProxy.Transport.RoundTrip(req)
if err != nil {
logger.Error("error making request for header extraction to ipfs: ", err)
return err
}
for _, h := range hdrs {
dest[h] = res.Header[h]
}
return nil
}
// setHeaders sets some headers for all hijacked endpoints:
// - First, we fix CORs headers by making an OPTIONS request to IPFS with the
// same Origin. Our objective is to get headers for non-preflight requests
// only (the ones we hijack).
// - Second, we add any of the one-time-extracted headers that we deem necessary
// or the user needs from IPFS (in case of custom headers).
// This may trigger a single POST request to ExtractHeaderPath if they
// were not extracted before or TTL has expired.
// - Third, we set our own headers.
func (proxy *Server) setHeaders(dest http.Header, srcRequest *http.Request) {
proxy.setCORSHeaders(dest, srcRequest)
proxy.setAdditionalIpfsHeaders(dest, srcRequest)
proxy.setClusterProxyHeaders(dest, srcRequest)
}
// see setHeaders
func (proxy *Server) setCORSHeaders(dest http.Header, srcRequest *http.Request) {
// Fix CORS headers by making an OPTIONS request
// The request URL only has a valid Path(). See http.Request docs.
srcURL := fmt.Sprintf("%s%s", proxy.nodeAddr, srcRequest.URL.Path)
req, err := http.NewRequest(http.MethodOptions, srcURL, nil)
if err != nil { // this should really not happen.
logger.Error(err)
return
}
req.Header["Origin"] = srcRequest.Header["Origin"]
req.Header.Set("Access-Control-Request-Method", srcRequest.Method)
// error is logged. We proceed if request failed.
proxy.copyHeadersFromIPFSWithRequest(corsHeaders, dest, req)
}
// see setHeaders
func (proxy *Server) setAdditionalIpfsHeaders(dest http.Header, srcRequest *http.Request) {
// Avoid re-requesting these if we have them
if ok := proxy.setIPFSHeaders(dest); ok {
return
}
srcURL := fmt.Sprintf("%s%s", proxy.nodeAddr, proxy.config.ExtractHeadersPath)
req, err := http.NewRequest(http.MethodPost, srcURL, nil)
if err != nil {
logger.Error("error extracting additional headers from ipfs", err)
return
}
// error is logged. We proceed if request failed.
proxy.copyHeadersFromIPFSWithRequest(
proxy.ipfsHeaders(),
dest,
req,
)
proxy.rememberIPFSHeaders(dest)
}
// see setHeaders
func (proxy *Server) setClusterProxyHeaders(dest http.Header, srcRequest *http.Request) {
dest.Set("Content-Type", "application/json")
dest.Set("Server", fmt.Sprintf("rep-mgr/ipfsproxy/%s", version.Version))
}