Skip to content

Commit

Permalink
feat: expose routing v1 server via optional setting
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed May 15, 2023
1 parent 4acadd4 commit e57dd07
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 8 deletions.
4 changes: 4 additions & 0 deletions cmd/ipfs/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,10 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
opts = append(opts, corehttp.P2PProxyOption())
}

if cfg.Gateway.ExposeRoutingAPI.WithDefault(config.DefaultExposeRoutingAPI) {
opts = append(opts, corehttp.RoutingOption())
}

if len(cfg.Gateway.RootRedirect) > 0 {
opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
}
Expand Down
9 changes: 8 additions & 1 deletion config/gateway.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package config

const DefaultInlineDNSLink = false
const (
DefaultInlineDNSLink = false
DefaultExposeRoutingAPI = false
)

type GatewaySpec struct {
// Paths is explicit list of path prefixes that should be handled by
Expand Down Expand Up @@ -59,4 +62,8 @@ type Gateway struct {
// PublicGateways configures behavior of known public gateways.
// Each key is a fully qualified domain name (FQDN).
PublicGateways map[string]*GatewaySpec

// ExposeRoutingAPI configures the gateway to expose a Routing v1 HTTP Server
// under /routing/v1: https://specs.ipfs.tech/routing/routing-v1/.
ExposeRoutingAPI Flag
}
111 changes: 111 additions & 0 deletions core/corehttp/routing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package corehttp

import (
"context"
"net"
"net/http"
"time"

"github.com/ipfs/boxo/routing/http/server"
"github.com/ipfs/boxo/routing/http/types"
"github.com/ipfs/boxo/routing/http/types/iter"
cid "github.com/ipfs/go-cid"
core "github.com/ipfs/kubo/core"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/routing"
"github.com/multiformats/go-multiaddr"
)

const (
streamingProvidersCount = 0
nonStreamingProvidersCount = 20
)

func RoutingOption() ServeOption {
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
handler := server.Handler(&contentRouter{n})
mux.Handle("/routing/v1/", handler)
return mux, nil
}
}

type contentRouter struct {
n *core.IpfsNode
}

func (r *contentRouter) FindProviders(ctx context.Context, key cid.Cid, streaming bool) (iter.ResultIter[types.ProviderResponse], error) {
ctx, cancel := context.WithCancel(ctx)
count := nonStreamingProvidersCount
if streaming {
count = streamingProvidersCount
}
ch := r.n.Routing.FindProvidersAsync(ctx, key, count)
return iter.ToResultIter[types.ProviderResponse](&peerChanIter{
ch: ch,
cancel: cancel,
}), nil
}

func (r *contentRouter) Provide(ctx context.Context, req *server.WriteProvideRequest) (types.ProviderResponse, error) {
// Kubo /routing/v1 endpoint does not support write operations.
return nil, routing.ErrNotSupported
}

func (r *contentRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) {
// Kubo /routing/v1 endpoint does not support write operations.
return 0, routing.ErrNotSupported
}

type peerChanIter struct {
ch <-chan peer.AddrInfo
cancel context.CancelFunc
next *peer.AddrInfo
}

func (it *peerChanIter) Next() bool {
addr, ok := <-it.ch
if ok {
it.next = &addr
return true
} else {
it.next = nil
return false
}
}

func (it *peerChanIter) Val() types.ProviderResponse {
if it.next == nil {
return nil
}

// We don't know what type of protocol this peer provides. It is likely Bitswap
// but it might not be. Therefore, we set an unknown protocol with an unknown schema.
rec := &providerRecord{
Protocol: "transport-unknown",
Schema: "unknown",
ID: it.next.ID,
Addrs: it.next.Addrs,
}

return rec
}

func (it *peerChanIter) Close() error {
it.cancel()
return nil
}

type providerRecord struct {
Protocol string
Schema string
ID peer.ID
Addrs []multiaddr.Multiaddr
}

func (pr *providerRecord) GetProtocol() string {
return pr.Protocol
}

func (pr *providerRecord) GetSchema() string {
return pr.Schema
}
2 changes: 1 addition & 1 deletion docs/examples/kubo-as-a-library/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ go 1.18
replace github.com/ipfs/kubo => ./../../..

require (
github.com/ipfs/boxo v0.8.2-0.20230510114019-33e3f0cd052b
github.com/ipfs/boxo v0.8.2-0.20230515105410-d96e912ecb44
github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
github.com/libp2p/go-libp2p v0.27.3
github.com/multiformats/go-multiaddr v0.9.0
Expand Down
4 changes: 2 additions & 2 deletions docs/examples/kubo-as-a-library/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
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/boxo v0.8.2-0.20230510114019-33e3f0cd052b h1:6EVpfwbBgwhfZOA19i55jOGokKOy+OaQAm1dg4RbXmc=
github.com/ipfs/boxo v0.8.2-0.20230510114019-33e3f0cd052b/go.mod h1:Ej2r08Z4VIaFKqY08UXMNhwcLf6VekHhK8c+KqA1B9Y=
github.com/ipfs/boxo v0.8.2-0.20230515105410-d96e912ecb44 h1:C5U/SZW51/AiY3t4dgC0BWvP/4U5v5zgrHIWS7N5OeM=
github.com/ipfs/boxo v0.8.2-0.20230515105410-d96e912ecb44/go.mod h1:Ej2r08Z4VIaFKqY08UXMNhwcLf6VekHhK8c+KqA1B9Y=
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY=
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/gogo/protobuf v1.3.2
github.com/google/uuid v1.3.0
github.com/hashicorp/go-multierror v1.1.1
github.com/ipfs/boxo v0.8.2-0.20230510114019-33e3f0cd052b
github.com/ipfs/boxo v0.8.2-0.20230515105410-d96e912ecb44
github.com/ipfs/go-block-format v0.1.2
github.com/ipfs/go-cid v0.4.1
github.com/ipfs/go-cidutil v0.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
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/boxo v0.8.2-0.20230510114019-33e3f0cd052b h1:6EVpfwbBgwhfZOA19i55jOGokKOy+OaQAm1dg4RbXmc=
github.com/ipfs/boxo v0.8.2-0.20230510114019-33e3f0cd052b/go.mod h1:Ej2r08Z4VIaFKqY08UXMNhwcLf6VekHhK8c+KqA1B9Y=
github.com/ipfs/boxo v0.8.2-0.20230515105410-d96e912ecb44 h1:C5U/SZW51/AiY3t4dgC0BWvP/4U5v5zgrHIWS7N5OeM=
github.com/ipfs/boxo v0.8.2-0.20230515105410-d96e912ecb44/go.mod h1:Ej2r08Z4VIaFKqY08UXMNhwcLf6VekHhK8c+KqA1B9Y=
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY=
Expand Down
2 changes: 1 addition & 1 deletion test/cli/content_routing_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type fakeHTTPContentRouter struct {
provideCalls int
}

func (r *fakeHTTPContentRouter) FindProviders(ctx context.Context, key cid.Cid) (iter.ResultIter[types.ProviderResponse], error) {
func (r *fakeHTTPContentRouter) FindProviders(ctx context.Context, key cid.Cid, stream bool) (iter.ResultIter[types.ProviderResponse], error) {
r.m.Lock()
defer r.m.Unlock()
r.findProvidersCalls++
Expand Down
92 changes: 92 additions & 0 deletions test/cli/routing_http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package cli

import (
"encoding/json"
"errors"
"io"
"net/http"
"strings"
"testing"

"github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/test/cli/harness"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/assert"
)

func TestRoutingV1(t *testing.T) {
t.Parallel()
nodes := harness.NewT(t).NewNodes(5).Init()
nodes.ForEachPar(func(node *harness.Node) {
node.UpdateConfig(func(cfg *config.Config) {
cfg.Gateway.ExposeRoutingAPI = config.True
cfg.Routing.Type = config.NewOptionalString("dht")
})
})
nodes.StartDaemons().Connect()

type record struct {
Protocol string
Schema string
ID peer.ID
Addrs []string
}

type providers struct {
Providers []record
}

t.Run("Non-streaming response with Accept: application/json", func(t *testing.T) {
t.Parallel()

cid := nodes[2].IPFSAddStr("hello world")
_ = nodes[3].IPFSAddStr("hello world")

resp := nodes[1].GatewayClient().Get("/routing/v1/providers/"+cid, func(r *http.Request) {
r.Header.Set("Accept", "application/json")
})
assert.Equal(t, resp.Headers.Get("Content-Type"), "application/json")
assert.Equal(t, http.StatusOK, resp.StatusCode)

var providers *providers
err := json.Unmarshal([]byte(resp.Body), &providers)
assert.NoError(t, err)

var peers []peer.ID
for _, prov := range providers.Providers {
peers = append(peers, prov.ID)
}
assert.Contains(t, peers, nodes[2].PeerID())
assert.Contains(t, peers, nodes[3].PeerID())
})

t.Run("Streaming response with Accept: application/x-ndjson", func(t *testing.T) {
t.Parallel()

cid := nodes[1].IPFSAddStr("hello world")
_ = nodes[4].IPFSAddStr("hello world")

resp := nodes[0].GatewayClient().Get("/routing/v1/providers/"+cid, func(r *http.Request) {
r.Header.Set("Accept", "application/x-ndjson")
})
assert.Equal(t, resp.Headers.Get("Content-Type"), "application/x-ndjson")
assert.Equal(t, http.StatusOK, resp.StatusCode)

var peers []peer.ID
dec := json.NewDecoder(strings.NewReader(resp.Body))

for {
var record *record
err := dec.Decode(&record)
if errors.Is(err, io.EOF) {
break
}

assert.NoError(t, err)
peers = append(peers, record.ID)
}

assert.Contains(t, peers, nodes[1].PeerID())
assert.Contains(t, peers, nodes[4].PeerID())
})
}

0 comments on commit e57dd07

Please sign in to comment.