From 7c4486878970b0e6e0e5e9ddaaf61f49dae0d105 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 31 Jan 2023 12:39:59 +0100 Subject: [PATCH 1/2] feat(gateway): improve API interface, remove Writable API --- gateway/README.md | 4 +- gateway/gateway.go | 41 +++-- gateway/handler.go | 263 ++------------------------- gateway/handler_block.go | 10 +- gateway/handler_car.go | 7 +- gateway/handler_codec.go | 34 ++-- gateway/handler_ipns_record.go | 15 +- gateway/handler_tar.go | 2 +- gateway/handler_unixfs.go | 2 +- gateway/handler_unixfs__redirects.go | 6 +- gateway/handler_unixfs_dir.go | 4 +- go.mod | 12 +- go.sum | 14 -- 13 files changed, 81 insertions(+), 333 deletions(-) diff --git a/gateway/README.md b/gateway/README.md index fdfaa6c30..6c7a7cf47 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -15,18 +15,16 @@ headers := map[string][]string{} gateway.AddAccessControlHeaders(headers) conf := gateway.Config{ - Writable: false, Headers: headers, } // Initialize a NodeAPI interface for both an online and offline versions. // The offline version should not make any network request for missing content. ipfs := ... -offlineIPFS := ... // Create http mux and setup path gateway handler. mux := http.NewServeMux() -gwHandler := gateway.NewHandler(conf, ipfs, offlineIPFS) +gwHandler := gateway.NewHandler(conf, ipfs) mux.Handle("/ipfs/", gwHandler) mux.Handle("/ipns/", gwHandler) diff --git a/gateway/gateway.go b/gateway/gateway.go index 0882d4fb4..2432d6274 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -5,33 +5,38 @@ import ( "net/http" "sort" - coreiface "github.com/ipfs/interface-go-ipfs-core" - path "github.com/ipfs/interface-go-ipfs-core/path" + cid "github.com/ipfs/go-cid" + "github.com/ipfs/go-libipfs/blocks" + "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" ) -// Config is the configuration that will be applied when creating a new gateway -// handler. +// Config is the configuration used when creating a new gateway handler. type Config struct { - Headers map[string][]string - Writable bool + Headers map[string][]string } -// 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 +// API defines the minimal set of API services required for a gateway handler. +type API interface { + // GetUnixFsNode returns a read-only handle to a file tree referenced by a path. + GetUnixFsNode(context.Context, path.Path) (files.Node, error) - // Block returns an implementation of Block API - Block() coreiface.BlockAPI + // LsUnixFsDir returns the list of links in a directory. + LsUnixFsDir(context.Context, path.Path, ...options.UnixfsLsOption) (<-chan iface.DirEntry, error) - // Dag returns an implementation of Dag API - Dag() coreiface.APIDagService + // GetBlock return a block from a certain CID. + GetBlock(context.Context, cid.Cid) (blocks.Block, error) - // Routing returns an implementation of Routing API. - // Used for returning signed IPNS records, see IPIP-0328 - Routing() coreiface.RoutingAPI + // GetIPNSRecord retrieves the best IPNS record for a given CID (libp2p-key) + // from the routing system. + GetIPNSRecord(context.Context, cid.Cid) ([]byte, error) - // ResolvePath resolves the path using Unixfs resolver + // IsCached returns whether or not the path exists locally. + IsCached(context.Context, path.Path) bool + + // ResolvePath resolves the path using UnixFS resolver ResolvePath(context.Context, path.Path) (path.Resolved, error) } diff --git a/gateway/handler.go b/gateway/handler.go index e6354069a..50f7aabc6 100644 --- a/gateway/handler.go +++ b/gateway/handler.go @@ -9,7 +9,6 @@ import ( "net/http" "net/textproto" "net/url" - "os" gopath "path" "regexp" "runtime/debug" @@ -18,11 +17,7 @@ import ( cid "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" - "github.com/ipfs/go-libipfs/files" logging "github.com/ipfs/go-log" - dag "github.com/ipfs/go-merkledag" - mfs "github.com/ipfs/go-mfs" - path "github.com/ipfs/go-path" "github.com/ipfs/go-path/resolver" coreiface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" @@ -71,9 +66,8 @@ type redirectTemplateData struct { // handler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/) // (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link) type handler struct { - config Config - api NodeAPI - offlineAPI NodeAPI + config Config + api API // generic metrics firstContentBlockGetMetric *prometheus.HistogramVec @@ -219,15 +213,14 @@ 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 { - return newHandler(c, api, offlineAPI) +func NewHandler(c Config, api API) http.Handler { + return newHandler(c, api) } -func newHandler(c Config, api NodeAPI, offlineAPI NodeAPI) *handler { +func newHandler(c Config, api API) *handler { i := &handler{ - config: c, - api: api, - offlineAPI: offlineAPI, + config: c, + api: api, // Improved Metrics // ---------------------------- // Time till the first content block (bar in /ipfs/cid/foo/bar) @@ -271,26 +264,6 @@ func newHandler(c Config, api NodeAPI, offlineAPI NodeAPI) *handler { return i } -func parseIpfsPath(p string) (cid.Cid, string, error) { - rootPath, err := path.ParsePath(p) - if err != nil { - return cid.Cid{}, "", err - } - - // Check the path. - rsegs := rootPath.Segments() - if rsegs[0] != "ipfs" { - return cid.Cid{}, "", fmt.Errorf("WritableGateway: only ipfs paths supported") - } - - rootCid, err := cid.Decode(rsegs[1]) - if err != nil { - return cid.Cid{}, "", err - } - - return rootCid, path.Join(rsegs[2:]), nil -} - func (i *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // the hour is a hard fallback, we don't expect it to happen, but just in case ctx, cancel := context.WithTimeout(r.Context(), time.Hour) @@ -305,20 +278,6 @@ func (i *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } }() - if i.config.Writable { - switch r.Method { - case http.MethodPost: - i.postHandler(w, r) - return - case http.MethodPut: - i.putHandler(w, r) - return - case http.MethodDelete: - i.deleteHandler(w, r) - return - } - } - switch r.Method { case http.MethodGet, http.MethodHead: i.getOrHeadHandler(w, r) @@ -328,19 +287,12 @@ func (i *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - errmsg := "Method " + r.Method + " not allowed: " - var status int - if !i.config.Writable { - status = http.StatusMethodNotAllowed - errmsg = errmsg + "read only access" - w.Header().Add("Allow", http.MethodGet) - w.Header().Add("Allow", http.MethodHead) - w.Header().Add("Allow", http.MethodOptions) - } else { - status = http.StatusBadRequest - errmsg = errmsg + "bad request for " + r.URL.Path - } - http.Error(w, errmsg, status) + w.Header().Add("Allow", http.MethodGet) + w.Header().Add("Allow", http.MethodHead) + w.Header().Add("Allow", http.MethodOptions) + + errmsg := "Method " + r.Method + " not allowed: read only access" + http.Error(w, errmsg, http.StatusMethodNotAllowed) } func (i *handler) optionsHandler(w http.ResponseWriter, r *http.Request) { @@ -459,190 +411,6 @@ 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)) - if err != nil { - internalWebError(w, err) - return - } - - i.addUserHeaders(w) // ok, _now_ write user's headers. - w.Header().Set("IPFS-Hash", p.Cid().String()) - log.Debugw("CID created, http redirect", "from", r.URL, "to", p, "status", http.StatusCreated) - http.Redirect(w, r, p.String(), http.StatusCreated) -} - -func (i *handler) putHandler(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - ds := i.api.Dag() - - // Parse the path - rootCid, newPath, err := parseIpfsPath(r.URL.Path) - if err != nil { - webError(w, "WritableGateway: failed to parse the path", err, http.StatusBadRequest) - return - } - if newPath == "" || newPath == "/" { - http.Error(w, "WritableGateway: empty path", http.StatusBadRequest) - return - } - newDirectory, newFileName := gopath.Split(newPath) - - // Resolve the old root. - - rnode, err := ds.Get(ctx, rootCid) - if err != nil { - webError(w, "WritableGateway: Could not create DAG from request", err, http.StatusInternalServerError) - return - } - - pbnd, ok := rnode.(*dag.ProtoNode) - if !ok { - webError(w, "Cannot read non protobuf nodes through gateway", dag.ErrNotProtobuf, http.StatusBadRequest) - return - } - - // Create the new file. - newFilePath, err := i.api.Unixfs().Add(ctx, files.NewReaderFile(r.Body)) - if err != nil { - webError(w, "WritableGateway: could not create DAG from request", err, http.StatusInternalServerError) - return - } - - newFile, err := ds.Get(ctx, newFilePath.Cid()) - if err != nil { - webError(w, "WritableGateway: failed to resolve new file", err, http.StatusInternalServerError) - return - } - - // Patch the new file into the old root. - - root, err := mfs.NewRoot(ctx, ds, pbnd, nil) - if err != nil { - webError(w, "WritableGateway: failed to create MFS root", err, http.StatusBadRequest) - return - } - - if newDirectory != "" { - err := mfs.Mkdir(root, newDirectory, mfs.MkdirOpts{Mkparents: true, Flush: false}) - if err != nil { - webError(w, "WritableGateway: failed to create MFS directory", err, http.StatusInternalServerError) - return - } - } - dirNode, err := mfs.Lookup(root, newDirectory) - if err != nil { - webError(w, "WritableGateway: failed to lookup directory", err, http.StatusInternalServerError) - return - } - dir, ok := dirNode.(*mfs.Directory) - if !ok { - http.Error(w, "WritableGateway: target directory is not a directory", http.StatusBadRequest) - return - } - err = dir.Unlink(newFileName) - switch err { - case os.ErrNotExist, nil: - default: - webError(w, "WritableGateway: failed to replace existing file", err, http.StatusBadRequest) - return - } - err = dir.AddChild(newFileName, newFile) - if err != nil { - webError(w, "WritableGateway: failed to link file into directory", err, http.StatusInternalServerError) - return - } - nnode, err := root.GetDirectory().GetNode() - if err != nil { - webError(w, "WritableGateway: failed to finalize", err, http.StatusInternalServerError) - return - } - newcid := nnode.Cid() - - i.addUserHeaders(w) // ok, _now_ write user's headers. - w.Header().Set("IPFS-Hash", newcid.String()) - - redirectURL := gopath.Join(ipfsPathPrefix, newcid.String(), newPath) - log.Debugw("CID replaced, redirect", "from", r.URL, "to", redirectURL, "status", http.StatusCreated) - http.Redirect(w, r, redirectURL, http.StatusCreated) -} - -func (i *handler) deleteHandler(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - // parse the path - - rootCid, newPath, err := parseIpfsPath(r.URL.Path) - if err != nil { - webError(w, "WritableGateway: failed to parse the path", err, http.StatusBadRequest) - return - } - if newPath == "" || newPath == "/" { - http.Error(w, "WritableGateway: empty path", http.StatusBadRequest) - return - } - directory, filename := gopath.Split(newPath) - - // lookup the root - - rootNodeIPLD, err := i.api.Dag().Get(ctx, rootCid) - if err != nil { - webError(w, "WritableGateway: failed to resolve root CID", err, http.StatusInternalServerError) - return - } - rootNode, ok := rootNodeIPLD.(*dag.ProtoNode) - if !ok { - http.Error(w, "WritableGateway: empty path", http.StatusInternalServerError) - return - } - - // construct the mfs root - - root, err := mfs.NewRoot(ctx, i.api.Dag(), rootNode, nil) - if err != nil { - webError(w, "WritableGateway: failed to construct the MFS root", err, http.StatusBadRequest) - return - } - - // lookup the parent directory - - parentNode, err := mfs.Lookup(root, directory) - if err != nil { - webError(w, "WritableGateway: failed to look up parent", err, http.StatusInternalServerError) - return - } - - parent, ok := parentNode.(*mfs.Directory) - if !ok { - http.Error(w, "WritableGateway: parent is not a directory", http.StatusInternalServerError) - return - } - - // delete the file - - switch parent.Unlink(filename) { - case nil, os.ErrNotExist: - default: - webError(w, "WritableGateway: failed to remove file", err, http.StatusInternalServerError) - return - } - - nnode, err := root.GetDirectory().GetNode() - if err != nil { - webError(w, "WritableGateway: failed to finalize", err, http.StatusInternalServerError) - return - } - ncid := nnode.Cid() - - i.addUserHeaders(w) // ok, _now_ write user's headers. - w.Header().Set("IPFS-Hash", ncid.String()) - - redirectURL := gopath.Join(ipfsPathPrefix+ncid.String(), directory) - // note: StatusCreated is technically correct here as we created a new resource. - log.Debugw("CID deleted, redirect", "from", r.RequestURI, "to", redirectURL, "status", http.StatusCreated) - http.Redirect(w, r, redirectURL, http.StatusCreated) -} - func (i *handler) addUserHeaders(w http.ResponseWriter) { for k, v := range i.config.Headers { w.Header()[k] = v @@ -978,8 +746,7 @@ func (i *handler) handlePathResolution(w http.ResponseWriter, r *http.Request, r // https://github.com/ipfs/specs/blob/main/http-gateways/PATH_GATEWAY.md#cache-control-request-header func (i *handler) handleOnlyIfCached(w http.ResponseWriter, r *http.Request, contentPath ipath.Path, logger *zap.SugaredLogger) (requestHandled bool) { if r.Header.Get("Cache-Control") == "only-if-cached" { - _, err := i.offlineAPI.Block().Stat(r.Context(), contentPath) - if err != nil { + if !i.api.IsCached(r.Context(), contentPath) { if r.Method == http.MethodHead { w.WriteHeader(http.StatusPreconditionFailed) return true @@ -1096,7 +863,7 @@ func handleSuperfluousNamespace(w http.ResponseWriter, r *http.Request, contentP func (i *handler) handleGettingFirstBlock(r *http.Request, begin time.Time, contentPath ipath.Path, resolvedPath ipath.Resolved) *requestError { // Update the global metric of the time it takes to read the final root block of the requested resource // NOTE: for legacy reasons this happens before we go into content-type specific code paths - _, err := i.api.Block().Get(r.Context(), resolvedPath) + _, err := i.api.GetBlock(r.Context(), resolvedPath.Cid()) if err != nil { return newRequestError("ipfs block get "+resolvedPath.Cid().String(), err, http.StatusInternalServerError) } diff --git a/gateway/handler_block.go b/gateway/handler_block.go index 23a22f447..d9e1b137b 100644 --- a/gateway/handler_block.go +++ b/gateway/handler_block.go @@ -3,7 +3,6 @@ package gateway import ( "bytes" "context" - "io" "net/http" "time" @@ -17,17 +16,12 @@ func (i *handler) serveRawBlock(ctx context.Context, w http.ResponseWriter, r *h ctx, span := spanTrace(ctx, "ServeRawBlock", trace.WithAttributes(attribute.String("path", resolvedPath.String()))) defer span.End() blockCid := resolvedPath.Cid() - blockReader, err := i.api.Block().Get(ctx, resolvedPath) + block, err := i.api.GetBlock(ctx, blockCid) if err != nil { webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError) return } - block, err := io.ReadAll(blockReader) - if err != nil { - webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError) - return - } - content := bytes.NewReader(block) + content := bytes.NewReader(block.RawData()) // Set Content-Disposition var name string diff --git a/gateway/handler_car.go b/gateway/handler_car.go index f58bccfd7..b52b113ac 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" @@ -68,7 +67,7 @@ func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.R w.Header().Set("X-Content-Type-Options", "nosniff") // no funny business in the browsers :^) // Same go-car settings as dag.export command - store := dagStore{dag: i.api.Dag(), ctx: ctx} + store := dagStore{api: i.api, ctx: ctx} // TODO: support selectors passed as request param: https://github.com/ipfs/kubo/issues/8769 dag := gocar.Dag{Root: rootCid, Selector: selectorparse.CommonSelector_ExploreAllRecursively} @@ -89,10 +88,10 @@ 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 + api API ctx context.Context } func (ds dagStore) Get(_ context.Context, c cid.Cid) (blocks.Block, error) { - return ds.dag.Get(ds.ctx, c) + return ds.api.GetBlock(ds.ctx, c) } diff --git a/gateway/handler_codec.go b/gateway/handler_codec.go index cd7b11371..ba981cd02 100644 --- a/gateway/handler_codec.go +++ b/gateway/handler_codec.go @@ -5,17 +5,15 @@ import ( "context" "fmt" "html" - "io" "net/http" "strings" "time" cid "github.com/ipfs/go-cid" - ipldlegacy "github.com/ipfs/go-ipld-legacy" "github.com/ipfs/go-libipfs/gateway/assets" ipath "github.com/ipfs/interface-go-ipfs-core/path" - "github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime/multicodec" + "github.com/ipld/go-ipld-prime/node/basicnode" mc "github.com/multiformats/go-multicodec" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -163,17 +161,12 @@ func (i *handler) serveCodecHTML(ctx context.Context, w http.ResponseWriter, r * // serveCodecRaw returns the raw block without any conversion func (i *handler) serveCodecRaw(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, name string, modtime time.Time) { blockCid := resolvedPath.Cid() - blockReader, err := i.api.Block().Get(ctx, resolvedPath) + block, err := i.api.GetBlock(ctx, blockCid) if err != nil { webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError) return } - block, err := io.ReadAll(blockReader) - if err != nil { - webError(w, "ipfs block get "+blockCid.String(), err, http.StatusInternalServerError) - return - } - content := bytes.NewReader(block) + content := bytes.NewReader(block.RawData()) // ServeContent will take care of // If-None-Match+Etag, Content-Length and range requests @@ -182,19 +175,26 @@ func (i *handler) serveCodecRaw(ctx context.Context, w http.ResponseWriter, r *h // serveCodecConverted returns payload converted to codec specified in toCodec func (i *handler) serveCodecConverted(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, toCodec mc.Code, modtime time.Time) { - obj, err := i.api.Dag().Get(ctx, resolvedPath.Cid()) + blockCid := resolvedPath.Cid() + block, err := i.api.GetBlock(ctx, blockCid) if err != nil { - webError(w, "ipfs dag get "+html.EscapeString(resolvedPath.String()), err, http.StatusInternalServerError) + webError(w, "ipfs block get "+html.EscapeString(resolvedPath.String()), err, http.StatusInternalServerError) return } - universal, ok := obj.(ipldlegacy.UniversalNode) - if !ok { - err = fmt.Errorf("%T is not a valid IPLD node", obj) + codec := blockCid.Prefix().Codec + decoder, err := multicodec.LookupDecoder(codec) + if err != nil { + webError(w, err.Error(), err, http.StatusInternalServerError) + return + } + + node := basicnode.Prototype.Any.NewBuilder() + err = decoder(node, bytes.NewReader(block.RawData())) + if err != nil { webError(w, err.Error(), err, http.StatusInternalServerError) return } - finalNode := universal.(ipld.Node) encoder, err := multicodec.LookupEncoder(uint64(toCodec)) if err != nil { @@ -204,7 +204,7 @@ func (i *handler) serveCodecConverted(ctx context.Context, w http.ResponseWriter // Ensure IPLD node conforms to the codec specification. var buf bytes.Buffer - err = encoder(finalNode, &buf) + err = encoder(node.Build(), &buf) if err != nil { webError(w, err.Error(), err, http.StatusInternalServerError) return diff --git a/gateway/handler_ipns_record.go b/gateway/handler_ipns_record.go index 47786c5b7..50d99e231 100644 --- a/gateway/handler_ipns_record.go +++ b/gateway/handler_ipns_record.go @@ -9,8 +9,8 @@ import ( "time" "github.com/gogo/protobuf/proto" + "github.com/ipfs/go-cid" ipns_pb "github.com/ipfs/go-ipns/pb" - path "github.com/ipfs/go-path" ipath "github.com/ipfs/interface-go-ipfs-core/path" "go.uber.org/zap" ) @@ -24,13 +24,20 @@ func (i *handler) serveIpnsRecord(ctx context.Context, w http.ResponseWriter, r key := contentPath.String() key = strings.TrimSuffix(key, "/") - if strings.Count(key, "/") > 2 { + key = strings.TrimPrefix(key, "/ipns/") + if strings.Count(key, "/") != 0 { err := errors.New("cannot find ipns key for subpath") webError(w, err.Error(), err, http.StatusBadRequest) return } - rawRecord, err := i.api.Routing().Get(ctx, key) + c, err := cid.Decode(key) + if err != nil { + webError(w, err.Error(), err, http.StatusBadRequest) + return + } + + rawRecord, err := i.api.GetIPNSRecord(ctx, c) if err != nil { webError(w, err.Error(), err, http.StatusInternalServerError) return @@ -60,7 +67,7 @@ func (i *handler) serveIpnsRecord(ctx context.Context, w http.ResponseWriter, r if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" { name = urlFilename } else { - name = path.SplitList(key)[2] + ".ipns-record" + name = key + ".ipns-record" } setContentDispositionHeader(w, name, "attachment") diff --git a/gateway/handler_tar.go b/gateway/handler_tar.go index f5a7a6713..decdf87c9 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.GetUnixFsNode(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 9962d468c..c443e4b32 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.GetUnixFsNode(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 98715cb2a..97d3ec5a5 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.GetUnixFsNode(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.GetUnixFsNode(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.GetUnixFsNode(r.Context(), resolved404Path) if err != nil { return false } diff --git a/gateway/handler_unixfs_dir.go b/gateway/handler_unixfs_dir.go index 6d3db7fd5..b04201ac3 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.GetUnixFsNode(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.LsUnixFsDir(ctx, resolvedPath, options.Unixfs.ResolveChildren(false), options.Unixfs.UseCumulativeSize(true), diff --git a/go.mod b/go.mod index 839fca5cf..cd213febb 100644 --- a/go.mod +++ b/go.mod @@ -23,13 +23,10 @@ require ( github.com/ipfs/go-ipfs-routing v0.3.0 github.com/ipfs/go-ipfs-util v0.0.2 github.com/ipfs/go-ipld-format v0.4.0 - github.com/ipfs/go-ipld-legacy v0.1.1 github.com/ipfs/go-ipns v0.3.0 github.com/ipfs/go-log v1.0.5 github.com/ipfs/go-log/v2 v2.5.1 - github.com/ipfs/go-merkledag v0.9.0 github.com/ipfs/go-metrics-interface v0.0.1 - github.com/ipfs/go-mfs v0.2.1 github.com/ipfs/go-path v0.3.0 github.com/ipfs/go-peertaskqueue v0.8.0 github.com/ipfs/interface-go-ipfs-core v0.10.0 @@ -58,7 +55,6 @@ require ( ) require ( - github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -70,17 +66,14 @@ require ( github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/huin/goupnp v1.0.3 // indirect github.com/ipfs/bbloom v0.0.4 // indirect - github.com/ipfs/go-bitfield v1.0.0 // indirect github.com/ipfs/go-block-format v0.1.1 // indirect github.com/ipfs/go-blockservice v0.5.0 // indirect github.com/ipfs/go-fetcher v1.6.1 // indirect - github.com/ipfs/go-ipfs-chunker v0.0.1 // indirect github.com/ipfs/go-ipfs-ds-help v1.1.0 // indirect - github.com/ipfs/go-ipfs-files v0.0.8 // indirect - github.com/ipfs/go-ipfs-posinfo v0.0.1 // indirect github.com/ipfs/go-ipfs-pq v0.0.2 // indirect github.com/ipfs/go-ipld-cbor v0.0.6 // indirect - github.com/ipfs/go-unixfs v0.3.1 // indirect + github.com/ipfs/go-ipld-legacy v0.1.1 // indirect + github.com/ipfs/go-merkledag v0.9.0 // indirect github.com/ipfs/go-verifcid v0.0.2 // indirect github.com/ipld/go-codec-dagpb v1.5.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect @@ -114,7 +107,6 @@ require ( github.com/stretchr/objx v0.5.0 // indirect github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb // indirect github.com/whyrusleeping/cbor-gen v0.0.0-20221220214510-0333c149dec0 // indirect - github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect go.uber.org/atomic v1.10.0 // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b // indirect diff --git a/go.sum b/go.sum index ed2737820..98732459b 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a h1:E/8AP5dFtMhl5KPJz66Kt9G0n+7Sn41Fy1wv9/jHOrc= -github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -343,8 +341,6 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/go-bitfield v1.0.0 h1:y/XHm2GEmD9wKngheWNNCNL0pzrWXZwCdQGv1ikXknQ= -github.com/ipfs/go-bitfield v1.0.0/go.mod h1:N/UiujQy+K+ceU1EF5EkVd1TNqevLrCQMIcAEPrdtus= github.com/ipfs/go-bitswap v0.1.0/go.mod h1:FFJEf18E9izuCqUtHxbWEvq+reg7o4CW5wSAE1wsxj0= github.com/ipfs/go-bitswap v0.1.2/go.mod h1:qxSWS4NXGs7jQ6zQvoPY3+NmOfHHG47mhkiLzBpJQIs= github.com/ipfs/go-bitswap v0.5.1/go.mod h1:P+ckC87ri1xFLvk74NlXdP0Kj9RmWAh4+H78sC6Qopo= @@ -398,7 +394,6 @@ github.com/ipfs/go-ipfs-blockstore v1.2.0 h1:n3WTeJ4LdICWs/0VSfjHrlqpPpl6MZ+ySd3 github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE= github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= -github.com/ipfs/go-ipfs-chunker v0.0.1 h1:cHUUxKFQ99pozdahi+uSC/3Y6HeRpi9oTeUHbE27SEw= github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcBFymgY4rSDLAw= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= @@ -415,9 +410,6 @@ github.com/ipfs/go-ipfs-exchange-offline v0.0.1/go.mod h1:WhHSFCVYX36H/anEKQboAz github.com/ipfs/go-ipfs-exchange-offline v0.1.1/go.mod h1:vTiBRIbzSwDD0OWm+i3xeT0mO7jG2cbJYatp3HPk5XY= github.com/ipfs/go-ipfs-exchange-offline v0.3.0 h1:c/Dg8GDPzixGd0MC8Jh6mjOwU57uYokgWRFidfvEkuA= github.com/ipfs/go-ipfs-files v0.0.3/go.mod h1:INEFm0LL2LWXBhNJ2PMIIb2w45hpXgPjNoE7yA8Y1d4= -github.com/ipfs/go-ipfs-files v0.0.8 h1:8o0oFJkJ8UkO/ABl8T6ac6tKF3+NIpj67aAB6ZpusRg= -github.com/ipfs/go-ipfs-files v0.0.8/go.mod h1:wiN/jSG8FKyk7N0WyctKSvq3ljIa2NNTiZB55kpTdOs= -github.com/ipfs/go-ipfs-posinfo v0.0.1 h1:Esoxj+1JgSjX0+ylc0hUmJCOv6V2vFoZiETLR6OtpRs= github.com/ipfs/go-ipfs-posinfo v0.0.1/go.mod h1:SwyeVP+jCwiDu0C313l/8jg6ZxM0qqtlt2a0vILTc1A= github.com/ipfs/go-ipfs-pq v0.0.1/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY= @@ -468,9 +460,6 @@ github.com/ipfs/go-merkledag v0.9.0 h1:DFC8qZ96Dz1hMT7dtIpcY524eFFDiEWAF8hNJHWW2 github.com/ipfs/go-merkledag v0.9.0/go.mod h1:bPHqkHt5OZ0p1n3iqPeDiw2jIBkjAytRjS3WSBwjq90= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= -github.com/ipfs/go-mfs v0.2.1 h1:5jz8+ukAg/z6jTkollzxGzhkl3yxm022Za9f2nL5ab8= -github.com/ipfs/go-mfs v0.2.1/go.mod h1:Woj80iuw4ajDnIP6+seRaoHpPsc9hmL0pk/nDNDWP88= -github.com/ipfs/go-path v0.2.1/go.mod h1:NOScsVgxfC/eIw4nz6OiGwK42PjaSJ4Y/ZFPn1Xe07I= github.com/ipfs/go-path v0.3.0 h1:tkjga3MtpXyM5v+3EbRvOHEoo+frwi4oumw5K+KYWyA= github.com/ipfs/go-path v0.3.0/go.mod h1:NOScsVgxfC/eIw4nz6OiGwK42PjaSJ4Y/ZFPn1Xe07I= github.com/ipfs/go-peertaskqueue v0.1.0/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3Vzz/jUmWw8Z0U= @@ -478,8 +467,6 @@ github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68 github.com/ipfs/go-peertaskqueue v0.8.0 h1:JyNO144tfu9bx6Hpo119zvbEL9iQ760FHOiJYsUjqaU= github.com/ipfs/go-peertaskqueue v0.8.0/go.mod h1:cz8hEnnARq4Du5TGqiWKgMr/BOSQ5XOgMOh1K5YYKKM= github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw= -github.com/ipfs/go-unixfs v0.3.1 h1:LrfED0OGfG98ZEegO4/xiprx2O+yS+krCMQSp7zLVv8= -github.com/ipfs/go-unixfs v0.3.1/go.mod h1:h4qfQYzghiIc8ZNFKiLMFWOTzrWIAtzYQ59W/pCFf1o= github.com/ipfs/go-unixfsnode v1.1.2 h1:aTsCdhwU0F4dMShMwYGroAj4v4EzSONLdoENebvTRb0= github.com/ipfs/go-unixfsnode v1.1.2/go.mod h1:5dcE2x03pyjHk4JjamXmunTMzz+VUtqvPwZjIEkfV6s= github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0= @@ -1084,7 +1071,6 @@ github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvS github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/cbor-gen v0.0.0-20221220214510-0333c149dec0 h1:obKzQ1ey5AJg5NKjgtTo/CKwLImVP4ETLRcsmzFJ4Qw= github.com/whyrusleeping/cbor-gen v0.0.0-20221220214510-0333c149dec0/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= -github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= From c7cc8b8c1ed441b4a894ba8de0788c195f607219 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Wed, 1 Feb 2023 12:05:56 +0100 Subject: [PATCH 2/2] refactor: further simplify interface --- gateway/gateway.go | 9 +++++---- gateway/handler_unixfs_dir.go | 18 ++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/gateway/gateway.go b/gateway/gateway.go index 2432d6274..f50d80332 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -9,7 +9,6 @@ import ( "github.com/ipfs/go-libipfs/blocks" "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" ) @@ -21,10 +20,10 @@ type Config struct { // API defines the minimal set of API services required for a gateway handler. type API interface { // GetUnixFsNode returns a read-only handle to a file tree referenced by a path. - GetUnixFsNode(context.Context, path.Path) (files.Node, error) + GetUnixFsNode(context.Context, path.Resolved) (files.Node, error) // LsUnixFsDir returns the list of links in a directory. - LsUnixFsDir(context.Context, path.Path, ...options.UnixfsLsOption) (<-chan iface.DirEntry, error) + LsUnixFsDir(context.Context, path.Resolved) (<-chan iface.DirEntry, error) // GetBlock return a block from a certain CID. GetBlock(context.Context, cid.Cid) (blocks.Block, error) @@ -36,7 +35,9 @@ type API interface { // IsCached returns whether or not the path exists locally. IsCached(context.Context, path.Path) bool - // ResolvePath resolves the path using UnixFS resolver + // ResolvePath resolves the path using UnixFS resolver. If the path does not + // exist due to a missing link, it should return an error of type: + // https://pkg.go.dev/github.com/ipfs/go-path@v0.3.0/resolver#ErrNoLink ResolvePath(context.Context, path.Path) (path.Resolved, error) } diff --git a/gateway/handler_unixfs_dir.go b/gateway/handler_unixfs_dir.go index b04201ac3..fe578b1db 100644 --- a/gateway/handler_unixfs_dir.go +++ b/gateway/handler_unixfs_dir.go @@ -14,7 +14,6 @@ import ( "github.com/ipfs/go-libipfs/gateway/assets" path "github.com/ipfs/go-path" "github.com/ipfs/go-path/resolver" - options "github.com/ipfs/interface-go-ipfs-core/options" ipath "github.com/ipfs/interface-go-ipfs-core/path" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -61,9 +60,15 @@ 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.GetUnixFsNode(ctx, idxPath) + idxResolvedPath, err := i.api.ResolvePath(ctx, idxPath) switch err.(type) { case nil: + idx, err := i.api.GetUnixFsNode(ctx, idxResolvedPath) + if err != nil { + internalWebError(w, err) + return + } + f, ok := idx.(files.File) if !ok { internalWebError(w, files.ErrNotReader) @@ -104,14 +109,7 @@ func (i *handler) serveDirectory(ctx context.Context, w http.ResponseWriter, r * return } - // 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.LsUnixFsDir(ctx, - resolvedPath, - options.Unixfs.ResolveChildren(false), - options.Unixfs.UseCumulativeSize(true), - ) + results, err := i.api.LsUnixFsDir(ctx, resolvedPath) if err != nil { internalWebError(w, err) return