Skip to content

Commit

Permalink
refactor(gateway): decouple read, write, and offline Node APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed Jan 31, 2023
1 parent 7346505 commit 43deb6b
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 45 deletions.
1 change: 0 additions & 1 deletion gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ headers := map[string][]string{}
gateway.AddAccessControlHeaders(headers)

conf := gateway.Config{
Writable: false,
Headers: headers,
}

Expand Down
73 changes: 73 additions & 0 deletions gateway/api.go
Original file line number Diff line number Diff line change
@@ -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
}
27 changes: 2 additions & 25 deletions gateway/gateway.go
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
20 changes: 10 additions & 10 deletions gateway/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions gateway/handler_car.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion gateway/handler_tar.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gateway/handler_unixfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions gateway/handler_unixfs__redirects.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions gateway/handler_unixfs_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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),
Expand Down

0 comments on commit 43deb6b

Please sign in to comment.