From 43deb6ba371943e6e159abd3ac01f3c41f8b3b04 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 31 Jan 2023 12:39:59 +0100 Subject: [PATCH] refactor(gateway): decouple read, write, and offline Node APIs --- gateway/README.md | 1 - gateway/api.go | 73 ++++++++++++++++++++++++++++ gateway/gateway.go | 27 +--------- gateway/handler.go | 20 ++++---- gateway/handler_car.go | 3 +- gateway/handler_tar.go | 2 +- gateway/handler_unixfs.go | 2 +- gateway/handler_unixfs__redirects.go | 6 +-- gateway/handler_unixfs_dir.go | 4 +- 9 files changed, 93 insertions(+), 45 deletions(-) create mode 100644 gateway/api.go diff --git a/gateway/README.md b/gateway/README.md index fdfaa6c302..85b9dca631 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -15,7 +15,6 @@ headers := map[string][]string{} gateway.AddAccessControlHeaders(headers) conf := gateway.Config{ - Writable: false, Headers: headers, } diff --git a/gateway/api.go b/gateway/api.go new file mode 100644 index 0000000000..7916baefc2 --- /dev/null +++ b/gateway/api.go @@ -0,0 +1,73 @@ +package gateway + +import ( + "context" + "io" + + cid "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "github.com/ipfs/go-libipfs/files" + iface "github.com/ipfs/interface-go-ipfs-core" + options "github.com/ipfs/interface-go-ipfs-core/options" + "github.com/ipfs/interface-go-ipfs-core/path" +) + +type ReaderDagAPI interface { + Get(context.Context, cid.Cid) (ipld.Node, error) +} + +type ReaderBlockAPI interface { + Get(context.Context, path.Path) (io.Reader, error) + Stat(context.Context, path.Path) (iface.BlockStat, error) +} + +type ReaderRoutingAPI interface { + Get(context.Context, string) ([]byte, error) +} + +type ReaderUnixFsAPI interface { + Get(context.Context, path.Path) (files.Node, error) + Ls(context.Context, path.Path, ...options.UnixfsLsOption) (<-chan iface.DirEntry, error) +} + +// ReaderAPI defines the minimal set of API services required for a read-only +// gateway handler. These are, for the most part, subsets of the core interface. +type ReaderAPI interface { + // UnixFs returns an implementation of Readable UnixFs API. + UnixFs() ReaderUnixFsAPI + + // Block returns an implementation of Readable Block API. + Block() ReaderBlockAPI + + // Dag returns an implementation of Readable Dag API. + Dag() ReaderDagAPI + + // Routing returns an implementation of Readable Routing API. + // Used for returning signed IPNS records, see IPIP-0328 + Routing() ReaderRoutingAPI + + // ResolvePath resolves the path using UnixFS resolver + ResolvePath(context.Context, path.Path) (path.Resolved, error) +} + +type OfflineBlockAPI interface { + Stat(context.Context, path.Path) (iface.BlockStat, error) +} + +// OfflineAPI defines the API services required to work in offline mode. This +// are used for caching handling when 'Cache-Control: only-if-cached'. +type OfflineAPI interface { + Block() OfflineBlockAPI +} + +type WriterUnixFsAPI interface { + Add(context.Context, files.Node, ...options.UnixfsAddOption) (path.Resolved, error) +} + +// WriterAPI defines the API services required to work with a writable gateway. +// These are full services from the core interface API. This is EXPERIMENTAL +// and CAN change in the future. +type WriterAPI interface { + UnixFs() WriterUnixFsAPI + Dag() iface.APIDagService +} diff --git a/gateway/gateway.go b/gateway/gateway.go index 0882d4fb43..07305b3207 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -1,38 +1,15 @@ package gateway import ( - "context" "net/http" "sort" - - coreiface "github.com/ipfs/interface-go-ipfs-core" - path "github.com/ipfs/interface-go-ipfs-core/path" ) // Config is the configuration that will be applied when creating a new gateway // handler. type Config struct { - Headers map[string][]string - Writable bool -} - -// NodeAPI defines the minimal set of API services required by a gateway handler -type NodeAPI interface { - // Unixfs returns an implementation of Unixfs API - Unixfs() coreiface.UnixfsAPI - - // Block returns an implementation of Block API - Block() coreiface.BlockAPI - - // Dag returns an implementation of Dag API - Dag() coreiface.APIDagService - - // Routing returns an implementation of Routing API. - // Used for returning signed IPNS records, see IPIP-0328 - Routing() coreiface.RoutingAPI - - // ResolvePath resolves the path using Unixfs resolver - ResolvePath(context.Context, path.Path) (path.Resolved, error) + Headers map[string][]string + WriterAPI WriterAPI } // A helper function to clean up a set of headers: diff --git a/gateway/handler.go b/gateway/handler.go index e6354069a6..8927e5e14d 100644 --- a/gateway/handler.go +++ b/gateway/handler.go @@ -72,8 +72,8 @@ type redirectTemplateData struct { // (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link) type handler struct { config Config - api NodeAPI - offlineAPI NodeAPI + api ReaderAPI + offlineAPI OfflineAPI // generic metrics firstContentBlockGetMetric *prometheus.HistogramVec @@ -219,11 +219,11 @@ func newHistogramMetric(name string, help string) *prometheus.HistogramVec { // NewHandler returns an http.Handler that can act as a gateway to IPFS content // offlineApi is a version of the API that should not make network requests for missing data -func NewHandler(c Config, api NodeAPI, offlineAPI NodeAPI) http.Handler { +func NewHandler(c Config, api ReaderAPI, offlineAPI OfflineAPI) http.Handler { return newHandler(c, api, offlineAPI) } -func newHandler(c Config, api NodeAPI, offlineAPI NodeAPI) *handler { +func newHandler(c Config, api ReaderAPI, offlineAPI OfflineAPI) *handler { i := &handler{ config: c, api: api, @@ -305,7 +305,7 @@ func (i *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } }() - if i.config.Writable { + if i.config.WriterAPI != nil { switch r.Method { case http.MethodPost: i.postHandler(w, r) @@ -330,7 +330,7 @@ func (i *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { errmsg := "Method " + r.Method + " not allowed: " var status int - if !i.config.Writable { + if i.config.WriterAPI == nil { status = http.StatusMethodNotAllowed errmsg = errmsg + "read only access" w.Header().Add("Allow", http.MethodGet) @@ -460,7 +460,7 @@ func (i *handler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) { } func (i *handler) postHandler(w http.ResponseWriter, r *http.Request) { - p, err := i.api.Unixfs().Add(r.Context(), files.NewReaderFile(r.Body)) + p, err := i.config.WriterAPI.UnixFs().Add(r.Context(), files.NewReaderFile(r.Body)) if err != nil { internalWebError(w, err) return @@ -503,7 +503,7 @@ func (i *handler) putHandler(w http.ResponseWriter, r *http.Request) { } // Create the new file. - newFilePath, err := i.api.Unixfs().Add(ctx, files.NewReaderFile(r.Body)) + newFilePath, err := i.config.WriterAPI.UnixFs().Add(ctx, files.NewReaderFile(r.Body)) if err != nil { webError(w, "WritableGateway: could not create DAG from request", err, http.StatusInternalServerError) return @@ -517,7 +517,7 @@ func (i *handler) putHandler(w http.ResponseWriter, r *http.Request) { // Patch the new file into the old root. - root, err := mfs.NewRoot(ctx, ds, pbnd, nil) + root, err := mfs.NewRoot(ctx, i.config.WriterAPI.Dag(), pbnd, nil) if err != nil { webError(w, "WritableGateway: failed to create MFS root", err, http.StatusBadRequest) return @@ -598,7 +598,7 @@ func (i *handler) deleteHandler(w http.ResponseWriter, r *http.Request) { // construct the mfs root - root, err := mfs.NewRoot(ctx, i.api.Dag(), rootNode, nil) + root, err := mfs.NewRoot(ctx, i.config.WriterAPI.Dag(), rootNode, nil) if err != nil { webError(w, "WritableGateway: failed to construct the MFS root", err, http.StatusBadRequest) return diff --git a/gateway/handler_car.go b/gateway/handler_car.go index f58bccfd7a..4e7a69792a 100644 --- a/gateway/handler_car.go +++ b/gateway/handler_car.go @@ -8,7 +8,6 @@ import ( cid "github.com/ipfs/go-cid" blocks "github.com/ipfs/go-libipfs/blocks" - coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" gocar "github.com/ipld/go-car" selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" @@ -89,7 +88,7 @@ func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.R // FIXME(@Jorropo): https://github.com/ipld/go-car/issues/315 type dagStore struct { - dag coreiface.APIDagService + dag ReaderDagAPI ctx context.Context } diff --git a/gateway/handler_tar.go b/gateway/handler_tar.go index f5a7a67137..d7c75ac7b6 100644 --- a/gateway/handler_tar.go +++ b/gateway/handler_tar.go @@ -23,7 +23,7 @@ func (i *handler) serveTAR(ctx context.Context, w http.ResponseWriter, r *http.R defer cancel() // Get Unixfs file - file, err := i.api.Unixfs().Get(ctx, resolvedPath) + file, err := i.api.UnixFs().Get(ctx, resolvedPath) if err != nil { webError(w, "ipfs cat "+html.EscapeString(contentPath.String()), err, http.StatusBadRequest) return diff --git a/gateway/handler_unixfs.go b/gateway/handler_unixfs.go index 9962d468c9..c1e568bca4 100644 --- a/gateway/handler_unixfs.go +++ b/gateway/handler_unixfs.go @@ -19,7 +19,7 @@ func (i *handler) serveUnixFS(ctx context.Context, w http.ResponseWriter, r *htt defer span.End() // Handling UnixFS - dr, err := i.api.Unixfs().Get(ctx, resolvedPath) + dr, err := i.api.UnixFs().Get(ctx, resolvedPath) if err != nil { webError(w, "ipfs cat "+html.EscapeString(contentPath.String()), err, http.StatusBadRequest) return diff --git a/gateway/handler_unixfs__redirects.go b/gateway/handler_unixfs__redirects.go index 98715cb2a5..2dfd273693 100644 --- a/gateway/handler_unixfs__redirects.go +++ b/gateway/handler_unixfs__redirects.go @@ -120,7 +120,7 @@ func (i *handler) handleRedirectsFileRules(w http.ResponseWriter, r *http.Reques func (i *handler) getRedirectRules(r *http.Request, redirectsFilePath ipath.Resolved) ([]redirects.Rule, error) { // Convert the path into a file node - node, err := i.api.Unixfs().Get(r.Context(), redirectsFilePath) + node, err := i.api.UnixFs().Get(r.Context(), redirectsFilePath) if err != nil { return nil, fmt.Errorf("could not get _redirects: %w", err) } @@ -170,7 +170,7 @@ func (i *handler) serve4xx(w http.ResponseWriter, r *http.Request, content4xxPat return err } - node, err := i.api.Unixfs().Get(r.Context(), resolved4xxPath) + node, err := i.api.UnixFs().Get(r.Context(), resolved4xxPath) if err != nil { return err } @@ -220,7 +220,7 @@ func (i *handler) serveLegacy404IfPresent(w http.ResponseWriter, r *http.Request return false } - dr, err := i.api.Unixfs().Get(r.Context(), resolved404Path) + dr, err := i.api.UnixFs().Get(r.Context(), resolved404Path) if err != nil { return false } diff --git a/gateway/handler_unixfs_dir.go b/gateway/handler_unixfs_dir.go index 6d3db7fd59..10510c6467 100644 --- a/gateway/handler_unixfs_dir.go +++ b/gateway/handler_unixfs_dir.go @@ -61,7 +61,7 @@ func (i *handler) serveDirectory(ctx context.Context, w http.ResponseWriter, r * // Check if directory has index.html, if so, serveFile idxPath := ipath.Join(contentPath, "index.html") - idx, err := i.api.Unixfs().Get(ctx, idxPath) + idx, err := i.api.UnixFs().Get(ctx, idxPath) switch err.(type) { case nil: f, ok := idx.(files.File) @@ -107,7 +107,7 @@ func (i *handler) serveDirectory(ctx context.Context, w http.ResponseWriter, r * // Optimization: use Unixfs.Ls without resolving children, but using the // cumulative DAG size as the file size. This allows for a fast listing // while keeping a good enough Size field. - results, err := i.api.Unixfs().Ls(ctx, + results, err := i.api.UnixFs().Ls(ctx, resolvedPath, options.Unixfs.ResolveChildren(false), options.Unixfs.UseCumulativeSize(true),