@cryptotaxi247 / kubo / commits / 58aaee00f

feat(gw): Cache-Control: only-if-cached

This implements the only-if-cached behavior documented in specs: https://github.com/ipfs/specs/blob/main/http-gateways/PATH_GATEWAY.md#cache-control-request-header https://github.com/ipfs/specs/blob/main/http-gateways/PATH_GATEWAY.md#only-if-cached-head-behavior

Marcin Rataj committed Jul 5, 2022 at 15:39 UTC 58aaee00f80cf913174cf47f8c40367b70b8a5df
3 files changed +67 -7
core/corehttp/gateway.go
+5 -1
@@ -92,12 +92,16 @@ func GatewayOption(writable bool, paths ...string) ServeOption {
92 "X-Ipfs-Roots",
93 }, headers[ACEHeadersName]...))
94
95 - var gateway http.Handler = newGatewayHandler(GatewayConfig{
95 + var gateway http.Handler
96 + gateway, err = newGatewayHandler(GatewayConfig{
97 Headers: headers,
98 Writable: writable,
99 PathPrefixes: cfg.Gateway.PathPrefixes,
100 FastDirIndexThreshold: int(cfg.Gateway.FastDirIndexThreshold.WithDefault(100)),
101 }, api)
102 + if err != nil {
103 + return nil, err
104 + }
105
106 gateway = otelhttp.NewHandler(gateway, "Gateway.Request")
107
core/corehttp/gateway_handler.go
+40 -6
@@ -24,6 +24,7 @@ import (
24 path "github.com/ipfs/go-path"
25 "github.com/ipfs/go-path/resolver"
26 coreiface "github.com/ipfs/interface-go-ipfs-core"
27 + options "github.com/ipfs/interface-go-ipfs-core/options"
28 ipath "github.com/ipfs/interface-go-ipfs-core/path"
29 routing "github.com/libp2p/go-libp2p-core/routing"
30 prometheus "github.com/prometheus/client_golang/prometheus"
@@ -66,8 +67,9 @@ type redirectTemplateData struct {
67 // gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
68 // (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
69 type gatewayHandler struct {
69 - config GatewayConfig
70 - api coreiface.CoreAPI
70 + config GatewayConfig
71 + api coreiface.CoreAPI
72 + offlineApi coreiface.CoreAPI
73
74 // generic metrics
75 firstContentBlockGetMetric *prometheus.HistogramVec
@@ -211,10 +213,15 @@ func newGatewayHistogramMetric(name string, help string) *prometheus.HistogramVe
213 return histogramMetric
214 }
215
214 -func newGatewayHandler(c GatewayConfig, api coreiface.CoreAPI) *gatewayHandler {
216 +func newGatewayHandler(c GatewayConfig, api coreiface.CoreAPI) (*gatewayHandler, error) {
217 + offlineApi, err := api.WithOptions(options.Api.Offline(true))
218 + if err != nil {
219 + return nil, err
220 + }
221 i := &gatewayHandler{
216 - config: c,
217 - api: api,
222 + config: c,
223 + api: api,
224 + offlineApi: offlineApi,
225 // Improved Metrics
226 // ----------------------------
227 // Time till the first content block (bar in /ipfs/cid/foo/bar)
@@ -255,7 +262,7 @@ func newGatewayHandler(c GatewayConfig, api coreiface.CoreAPI) *gatewayHandler {
262 "The time to receive the first UnixFS node on a GET from the gateway.",
263 ),
264 }
258 - return i
265 + return i, nil
266 }
267
268 func parseIpfsPath(p string) (cid.Cid, string, error) {
@@ -360,6 +367,11 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
367 }
368
369 contentPath := ipath.New(r.URL.Path)
370 +
371 + if requestHandled := i.handleOnlyIfCached(w, r, contentPath, logger); requestHandled {
372 + return
373 + }
374 +
375 if requestHandled := handleSuperfluousNamespace(w, r, contentPath); requestHandled {
376 return
377 }
@@ -956,6 +968,28 @@ func debugStr(path string) string {
968 return q
969 }
970
971 +// Detect 'Cache-Control: only-if-cached' in request and return data if it is already in the local datastore.
972 +// https://github.com/ipfs/specs/blob/main/http-gateways/PATH_GATEWAY.md#cache-control-request-header
973 +func (i *gatewayHandler) handleOnlyIfCached(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (requestHandled bool) {
974 + if r.Header.Get("Cache-Control") == "only-if-cached" {
975 + _, err := i.offlineApi.Block().Stat(r.Context(), contentPath)
976 + if err != nil {
977 + if r.Method == http.MethodHead {
978 + w.WriteHeader(http.StatusPreconditionFailed)
979 + return true
980 + }
981 + errMsg := fmt.Sprintf("%q not in local datastore", contentPath.String())
982 + http.Error(w, errMsg, http.StatusPreconditionFailed)
983 + return true
984 + }
985 + if r.Method == http.MethodHead {
986 + w.WriteHeader(http.StatusOK)
987 + return true
988 + }
989 + }
990 + return false
991 +}
992 +
993 func handleUnsupportedHeaders(r *http.Request) (err *requestError) {
994 // X-Ipfs-Gateway-Prefix was removed (https://github.com/ipfs/kubo/issues/7702)
995 // TODO: remove this after go-ipfs 0.13 ships
test/sharness/t0116-gateway-cache.sh
+22
@@ -67,6 +67,28 @@ test_expect_success "Prepare IPNS unixfs content path for testing" '
67 cat curl_ipns_file_output
68 '
69
70 +# Cache-Control: only-if-cached
71 + test_expect_success "HEAD for /ipfs/ with only-if-cached succeeds when in local datastore" '
72 + curl -sv -I -H "Cache-Control: only-if-cached" "http://127.0.0.1:$GWAY_PORT/ipfs/$ROOT1_CID/root2/root3/root4/index.html" > curl_onlyifcached_postitive_head 2>&1 &&
73 + cat curl_onlyifcached_postitive_head &&
74 + grep "< HTTP/1.1 200 OK" curl_onlyifcached_postitive_head
75 + '
76 + test_expect_success "HEAD for /ipfs/ with only-if-cached fails when not in local datastore" '
77 + curl -sv -I -H "Cache-Control: only-if-cached" "http://127.0.0.1:$GWAY_PORT/ipfs/$(date | ipfs add --only-hash -Q)" > curl_onlyifcached_negative_head 2>&1 &&
78 + cat curl_onlyifcached_negative_head &&
79 + grep "< HTTP/1.1 412 Precondition Failed" curl_onlyifcached_negative_head
80 + '
81 + test_expect_success "GET for /ipfs/ with only-if-cached succeeds when in local datastore" '
82 + curl -svX GET -H "Cache-Control: only-if-cached" "http://127.0.0.1:$GWAY_PORT/ipfs/$ROOT1_CID/root2/root3/root4/index.html" >/dev/null 2>curl_onlyifcached_postitive_out &&
83 + cat curl_onlyifcached_postitive_out &&
84 + grep "< HTTP/1.1 200 OK" curl_onlyifcached_postitive_out
85 + '
86 + test_expect_success "GET for /ipfs/ with only-if-cached fails when not in local datastore" '
87 + curl -svX GET -H "Cache-Control: only-if-cached" "http://127.0.0.1:$GWAY_PORT/ipfs/$(date | ipfs add --only-hash -Q)" >/dev/null 2>curl_onlyifcached_negative_out &&
88 + cat curl_onlyifcached_negative_out &&
89 + grep "< HTTP/1.1 412 Precondition Failed" curl_onlyifcached_negative_out
90 + '
91 +
92 # X-Ipfs-Path
93
94 ## dir generated listing