mirror of
https://github.com/ipfs/kubo.git
synced 2025-05-20 00:18:12 +08:00
244 lines
7.2 KiB
Go
244 lines
7.2 KiB
Go
package corehttp
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
|
|
"github.com/ipfs/boxo/blockservice"
|
|
iface "github.com/ipfs/boxo/coreiface"
|
|
"github.com/ipfs/boxo/coreiface/path"
|
|
"github.com/ipfs/boxo/exchange/offline"
|
|
"github.com/ipfs/boxo/files"
|
|
"github.com/ipfs/boxo/gateway"
|
|
"github.com/ipfs/boxo/namesys"
|
|
offlineroute "github.com/ipfs/boxo/routing/offline"
|
|
cid "github.com/ipfs/go-cid"
|
|
version "github.com/ipfs/kubo"
|
|
config "github.com/ipfs/kubo/config"
|
|
core "github.com/ipfs/kubo/core"
|
|
"github.com/ipfs/kubo/core/node"
|
|
"github.com/libp2p/go-libp2p/core/routing"
|
|
id "github.com/libp2p/go-libp2p/p2p/protocol/identify"
|
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
|
)
|
|
|
|
func GatewayOption(paths ...string) ServeOption {
|
|
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
|
|
cfg, err := n.Repo.Config()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
headers := make(map[string][]string, len(cfg.Gateway.HTTPHeaders))
|
|
for h, v := range cfg.Gateway.HTTPHeaders {
|
|
headers[http.CanonicalHeaderKey(h)] = v
|
|
}
|
|
|
|
gateway.AddAccessControlHeaders(headers)
|
|
|
|
gwConfig := gateway.Config{
|
|
Headers: headers,
|
|
}
|
|
|
|
gwAPI, err := newGatewayBackend(n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gw := gateway.NewHandler(gwConfig, gwAPI)
|
|
// TODO: Add otelhttp.WithPropagators(tracing.Propagator()) option to
|
|
// propagate traces through the gateway once we test this feature.
|
|
gw = otelhttp.NewHandler(gw, "Gateway.Request")
|
|
|
|
// By default, our HTTP handler is the gateway handler.
|
|
handler := gw.ServeHTTP
|
|
|
|
for _, p := range paths {
|
|
mux.HandleFunc(p+"/", handler)
|
|
}
|
|
|
|
return mux, nil
|
|
}
|
|
}
|
|
|
|
func HostnameOption() ServeOption {
|
|
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
|
|
cfg, err := n.Repo.Config()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
gwAPI, err := newGatewayBackend(n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
publicGateways := convertPublicGateways(cfg.Gateway.PublicGateways)
|
|
childMux := http.NewServeMux()
|
|
mux.HandleFunc("/", gateway.WithHostname(childMux, gwAPI, publicGateways, cfg.Gateway.NoDNSLink).ServeHTTP)
|
|
return childMux, nil
|
|
}
|
|
}
|
|
|
|
func VersionOption() ServeOption {
|
|
return func(_ *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
|
|
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprintf(w, "Commit: %s\n", version.CurrentCommit)
|
|
fmt.Fprintf(w, "Client Version: %s\n", version.GetUserAgentVersion())
|
|
fmt.Fprintf(w, "Protocol Version: %s\n", id.DefaultProtocolVersion)
|
|
})
|
|
return mux, nil
|
|
}
|
|
}
|
|
|
|
func newGatewayBackend(n *core.IpfsNode) (gateway.IPFSBackend, error) {
|
|
cfg, err := n.Repo.Config()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bserv := n.Blocks
|
|
var vsRouting routing.ValueStore = n.Routing
|
|
nsys := n.Namesys
|
|
if cfg.Gateway.NoFetch {
|
|
bserv = blockservice.New(bserv.Blockstore(), offline.Exchange(bserv.Blockstore()))
|
|
|
|
cs := cfg.Ipns.ResolveCacheSize
|
|
if cs == 0 {
|
|
cs = node.DefaultIpnsCacheSize
|
|
}
|
|
if cs < 0 {
|
|
return nil, fmt.Errorf("cannot specify negative resolve cache size")
|
|
}
|
|
|
|
vsRouting = offlineroute.NewOfflineRouter(n.Repo.Datastore(), n.RecordValidator)
|
|
nsys, err = namesys.NewNameSystem(vsRouting,
|
|
namesys.WithDatastore(n.Repo.Datastore()),
|
|
namesys.WithDNSResolver(n.DNSResolver),
|
|
namesys.WithCache(cs))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error constructing namesys: %w", err)
|
|
}
|
|
}
|
|
|
|
gw, err := gateway.NewBlocksGateway(bserv, gateway.WithValueStore(vsRouting), gateway.WithNameSystem(nsys))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &offlineGatewayErrWrapper{gwimpl: gw}, nil
|
|
}
|
|
|
|
type offlineGatewayErrWrapper struct {
|
|
gwimpl gateway.IPFSBackend
|
|
}
|
|
|
|
func offlineErrWrap(err error) error {
|
|
if errors.Is(err, iface.ErrOffline) {
|
|
return fmt.Errorf("%s : %w", err.Error(), gateway.ErrServiceUnavailable)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (o *offlineGatewayErrWrapper) Get(ctx context.Context, path gateway.ImmutablePath, ranges ...gateway.ByteRange) (gateway.ContentPathMetadata, *gateway.GetResponse, error) {
|
|
md, n, err := o.gwimpl.Get(ctx, path, ranges...)
|
|
err = offlineErrWrap(err)
|
|
return md, n, err
|
|
}
|
|
|
|
func (o *offlineGatewayErrWrapper) GetAll(ctx context.Context, path gateway.ImmutablePath) (gateway.ContentPathMetadata, files.Node, error) {
|
|
md, n, err := o.gwimpl.GetAll(ctx, path)
|
|
err = offlineErrWrap(err)
|
|
return md, n, err
|
|
}
|
|
|
|
func (o *offlineGatewayErrWrapper) GetBlock(ctx context.Context, path gateway.ImmutablePath) (gateway.ContentPathMetadata, files.File, error) {
|
|
md, n, err := o.gwimpl.GetBlock(ctx, path)
|
|
err = offlineErrWrap(err)
|
|
return md, n, err
|
|
}
|
|
|
|
func (o *offlineGatewayErrWrapper) Head(ctx context.Context, path gateway.ImmutablePath) (gateway.ContentPathMetadata, files.Node, error) {
|
|
md, n, err := o.gwimpl.Head(ctx, path)
|
|
err = offlineErrWrap(err)
|
|
return md, n, err
|
|
}
|
|
|
|
func (o *offlineGatewayErrWrapper) ResolvePath(ctx context.Context, path gateway.ImmutablePath) (gateway.ContentPathMetadata, error) {
|
|
md, err := o.gwimpl.ResolvePath(ctx, path)
|
|
err = offlineErrWrap(err)
|
|
return md, err
|
|
}
|
|
|
|
func (o *offlineGatewayErrWrapper) GetCAR(ctx context.Context, path gateway.ImmutablePath) (gateway.ContentPathMetadata, io.ReadCloser, <-chan error, error) {
|
|
md, data, errCh, err := o.gwimpl.GetCAR(ctx, path)
|
|
err = offlineErrWrap(err)
|
|
return md, data, errCh, err
|
|
}
|
|
|
|
func (o *offlineGatewayErrWrapper) IsCached(ctx context.Context, path path.Path) bool {
|
|
return o.gwimpl.IsCached(ctx, path)
|
|
}
|
|
|
|
func (o *offlineGatewayErrWrapper) GetIPNSRecord(ctx context.Context, c cid.Cid) ([]byte, error) {
|
|
rec, err := o.gwimpl.GetIPNSRecord(ctx, c)
|
|
err = offlineErrWrap(err)
|
|
return rec, err
|
|
}
|
|
|
|
func (o *offlineGatewayErrWrapper) ResolveMutable(ctx context.Context, path path.Path) (gateway.ImmutablePath, error) {
|
|
imPath, err := o.gwimpl.ResolveMutable(ctx, path)
|
|
err = offlineErrWrap(err)
|
|
return imPath, err
|
|
}
|
|
|
|
func (o *offlineGatewayErrWrapper) GetDNSLinkRecord(ctx context.Context, s string) (path.Path, error) {
|
|
p, err := o.gwimpl.GetDNSLinkRecord(ctx, s)
|
|
err = offlineErrWrap(err)
|
|
return p, err
|
|
}
|
|
|
|
var _ gateway.IPFSBackend = (*offlineGatewayErrWrapper)(nil)
|
|
|
|
var defaultPaths = []string{"/ipfs/", "/ipns/", "/api/", "/p2p/"}
|
|
|
|
var subdomainGatewaySpec = &gateway.Specification{
|
|
Paths: defaultPaths,
|
|
UseSubdomains: true,
|
|
}
|
|
|
|
var defaultKnownGateways = map[string]*gateway.Specification{
|
|
"localhost": subdomainGatewaySpec,
|
|
}
|
|
|
|
func convertPublicGateways(publicGateways map[string]*config.GatewaySpec) map[string]*gateway.Specification {
|
|
gws := map[string]*gateway.Specification{}
|
|
|
|
// First, implicit defaults such as subdomain gateway on localhost
|
|
for hostname, gw := range defaultKnownGateways {
|
|
gws[hostname] = gw
|
|
}
|
|
|
|
// Then apply values from Gateway.PublicGateways, if present in the config
|
|
for hostname, gw := range publicGateways {
|
|
if gw == nil {
|
|
// Remove any implicit defaults, if present. This is useful when one
|
|
// wants to disable subdomain gateway on localhost etc.
|
|
delete(gws, hostname)
|
|
continue
|
|
}
|
|
|
|
gws[hostname] = &gateway.Specification{
|
|
Paths: gw.Paths,
|
|
NoDNSLink: gw.NoDNSLink,
|
|
UseSubdomains: gw.UseSubdomains,
|
|
InlineDNSLink: gw.InlineDNSLink.WithDefault(config.DefaultInlineDNSLink),
|
|
}
|
|
}
|
|
|
|
return gws
|
|
}
|