Fix #382: Add TTL for cached headers

License: MIT
Signed-off-by: Hector Sanjuan <code@hector.link>
This commit is contained in:
Hector Sanjuan 2019-01-11 11:36:44 +01:00
parent 66525fe0c8
commit 2a1eb3c2f9
4 changed files with 70 additions and 4 deletions

View File

@ -55,7 +55,7 @@ func TestLoadJSON(t *testing.T) {
j = &jsonConfig{} j = &jsonConfig{}
json.Unmarshal(cfgJSON, j) json.Unmarshal(cfgJSON, j)
j.ExtractHeadersTTL = -10 j.ExtractHeadersTTL = "-10"
tst, _ = json.Marshal(j) tst, _ = json.Marshal(j)
err = cfg.LoadJSON(tst) err = cfg.LoadJSON(tst)
if err == nil { if err == nil {

View File

@ -3,6 +3,7 @@ package ipfsproxy
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"time"
"github.com/ipfs/ipfs-cluster/version" "github.com/ipfs/ipfs-cluster/version"
) )
@ -45,6 +46,8 @@ var extractHeadersDefault = []string{
"Access-Control-Expose-Headers", "Access-Control-Expose-Headers",
} }
const ipfsHeadersTimestampKey = "proxyHeadersTS"
// ipfsHeaders returns all the headers we want to extract-once from IPFS: a // ipfsHeaders returns all the headers we want to extract-once from IPFS: a
// concatenation of extractHeadersDefault and config.ExtractHeadersExtra. // concatenation of extractHeadersDefault and config.ExtractHeadersExtra.
func (proxy *Server) ipfsHeaders() []string { func (proxy *Server) ipfsHeaders() []string {
@ -57,14 +60,47 @@ func (proxy *Server) rememberIPFSHeaders(hdrs http.Header) {
for _, h := range proxy.ipfsHeaders() { for _, h := range proxy.ipfsHeaders() {
proxy.ipfsHeadersStore.Store(h, hdrs[h]) 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
} }
// rememberIPFSHeaders adds the known IPFS Headers to the destination // rememberIPFSHeaders adds the known IPFS Headers to the destination
// and returns true if we could set all the headers in the list. // 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 // False is used to determine if we need to make a request to try
// to extract these headers. // to extract these headers.
func (proxy *Server) setIPFSHeaders(dest http.Header) bool { func (proxy *Server) setIPFSHeaders(dest http.Header) bool {
r := true 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() { for _, h := range proxy.ipfsHeaders() {
v, ok := proxy.ipfsHeadersStore.Load(h) v, ok := proxy.ipfsHeadersStore.Load(h)
if !ok { if !ok {

View File

@ -8,6 +8,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"testing" "testing"
"time"
"github.com/ipfs/ipfs-cluster/api" "github.com/ipfs/ipfs-cluster/api"
"github.com/ipfs/ipfs-cluster/test" "github.com/ipfs/ipfs-cluster/test"
@ -30,7 +31,10 @@ func testIPFSProxy(t *testing.T) (*Server, *test.IpfsMock) {
cfg.Default() cfg.Default()
cfg.NodeAddr = nodeMAddr cfg.NodeAddr = nodeMAddr
cfg.ListenAddr = proxyMAddr cfg.ListenAddr = proxyMAddr
cfg.ExtractHeadersExtra = []string{test.IpfsCustomHeaderName} cfg.ExtractHeadersExtra = []string{
test.IpfsCustomHeaderName,
test.IpfsTimeHeaderName,
}
proxy, err := New(cfg) proxy, err := New(cfg)
if err != nil { if err != nil {
@ -518,6 +522,7 @@ func mustParseURL(rawurl string) *url.URL {
func TestHeaderExtraction(t *testing.T) { func TestHeaderExtraction(t *testing.T) {
proxy, mock := testIPFSProxy(t) proxy, mock := testIPFSProxy(t)
proxy.config.ExtractHeadersTTL = time.Second
defer mock.Close() defer mock.Close()
defer proxy.Shutdown() defer proxy.Shutdown()
@ -554,4 +559,25 @@ func TestHeaderExtraction(t *testing.T) {
if !strings.HasPrefix(res.Header.Get("Server"), "ipfs-cluster") { if !strings.HasPrefix(res.Header.Get("Server"), "ipfs-cluster") {
t.Error("wrong value for Server header") t.Error("wrong value for Server header")
} }
// Test ExtractHeaderTTL
t1 := res.Header.Get(test.IpfsTimeHeaderName)
res, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal("should forward requests to ipfs host: ", err)
}
t2 := res.Header.Get(test.IpfsTimeHeaderName)
if t1 != t2 {
t.Error("should have cached the headers during TTL")
}
time.Sleep(1200 * time.Millisecond)
res, err = http.DefaultClient.Do(req)
if err != nil {
t.Fatal("should forward requests to ipfs host: ", err)
}
res.Body.Close()
t3 := res.Header.Get(test.IpfsTimeHeaderName)
if t3 == t2 {
t.Error("should have refreshed the headers after TTL")
}
} }

View File

@ -9,10 +9,12 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/rs/cors"
"github.com/ipfs/ipfs-cluster/api" "github.com/ipfs/ipfs-cluster/api"
"github.com/ipfs/ipfs-cluster/state/mapstate" "github.com/ipfs/ipfs-cluster/state/mapstate"
"github.com/rs/cors"
cid "github.com/ipfs/go-cid" cid "github.com/ipfs/go-cid"
u "github.com/ipfs/go-ipfs-util" u "github.com/ipfs/go-ipfs-util"
@ -20,6 +22,7 @@ import (
var ( var (
IpfsCustomHeaderName = "X-Custom-Header" IpfsCustomHeaderName = "X-Custom-Header"
IpfsTimeHeaderName = "X-Time-Now"
IpfsCustomHeaderValue = "42" IpfsCustomHeaderValue = "42"
IpfsACAOrigin = "myorigin" IpfsACAOrigin = "myorigin"
) )
@ -128,6 +131,7 @@ func (m *IpfsMock) handler(w http.ResponseWriter, r *http.Request) {
p := r.URL.Path p := r.URL.Path
w.Header().Set(IpfsCustomHeaderName, IpfsCustomHeaderValue) w.Header().Set(IpfsCustomHeaderName, IpfsCustomHeaderValue)
w.Header().Set("Server", "ipfs-mock") w.Header().Set("Server", "ipfs-mock")
w.Header().Set(IpfsTimeHeaderName, fmt.Sprintf("%d", time.Now().Unix()))
endp := strings.TrimPrefix(p, "/api/v0/") endp := strings.TrimPrefix(p, "/api/v0/")
switch endp { switch endp {
case "id": case "id":