Skip to content

Commit 545288c

Browse files
committed
feat(examples): routing v1 client cli
1 parent e2730a5 commit 545288c

File tree

6 files changed

+213
-0
lines changed

6 files changed

+213
-0
lines changed

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ Let us know if you find any issue or if you want to contribute and add a new tut
99
- [Fetching a UnixFS file by CID](./unixfs-file-cid)
1010
- [Gateway backed by a CAR file](./gateway/car)
1111
- [Gateway backed by a remote blockstore and IPNS resolver](./gateway/proxy)
12+
- [Delegated Routing V1 Command Line Client](./routing-v1/client/)

examples/go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ require (
125125
github.com/quic-go/quic-go v0.38.0 // indirect
126126
github.com/quic-go/webtransport-go v0.5.3 // indirect
127127
github.com/raulk/go-watchdog v1.3.0 // indirect
128+
github.com/samber/lo v1.36.0 // indirect
128129
github.com/spaolacci/murmur3 v1.1.0 // indirect
129130
github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb // indirect
130131
github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect

examples/go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
216216
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
217217
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
218218
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
219+
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
219220
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
220221
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
221222
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -478,6 +479,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
478479
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
479480
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
480481
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
482+
github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw=
483+
github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
481484
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
482485
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
483486
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
@@ -530,6 +533,7 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
530533
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
531534
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
532535
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
536+
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
533537
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
534538
github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb h1:Ywfo8sUltxogBpFuMOFRrrSifO788kAFxmvVw31PtQQ=
535539
github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb/go.mod h1:ikPs9bRWicNw3S7XpJ8sK/smGwU9WcSVU3dy9qahYBM=

examples/routing-v1/client/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
routing-client

examples/routing-v1/client/README.md

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Delegated Routing V1 Command Line Client
2+
3+
This is an example of how to use the Delegated Routing V1 HTTP client from Boxo.
4+
In this package, we build a small command line tool that allows you to connect to
5+
a Routing V1 endpoint and fetch content providers, peer information, as well as
6+
IPNS records for a certain IPNS name.
7+
8+
## Build
9+
10+
```bash
11+
> go build -o routing-client
12+
```
13+
14+
## Usage
15+
16+
First, you will need a Routing V1 server. For that, you can potentially use [Kubo],
17+
which supports [exposing](https://github.com/ipfs/kubo/blob/master/docs/config.md#gatewayexposeroutingapi)
18+
a Routing V1 endpoint. For the commands below, we assume the endpoint is `http://127.0.0.1:8080`.
19+
20+
### Find CID Providers
21+
22+
To find providers, provide the flag `-c` with the [CID] of the content you're looking for:
23+
24+
```console
25+
$ ./routing-client -e http://127.0.0.1:8080 -c bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4
26+
27+
12D3KooWEfL19QqRGGLraaAYw1XA3dtDdVRYaHt6jymFxcuQo3Zm
28+
Protocols: []
29+
Addresses: [/ip4/163.47.51.218/tcp/28131]
30+
12D3KooWK53GAx2g2UUYfJHHjxDbVLeDgGxNMHXDWeJa5KgMhTD2
31+
Protocols: []
32+
Addresses: [/ip4/195.167.147.43/udp/8888/quic /ip4/195.167.147.43/tcp/8888]
33+
12D3KooWCpr8kACTRLKrPy4LPpSX7LXvKQ7eYqTmY8CBvgK5HZgB
34+
Protocols: []
35+
Addresses: [/ip4/163.47.49.234/tcp/28102]
36+
12D3KooWC9L4RjPGgqpzBUBkcVpKjJYofCkC5i5QdQftg1LdsFb2
37+
Protocols: []
38+
Addresses: [/ip4/198.244.201.187/tcp/4001]
39+
```
40+
41+
### Find Peer Information
42+
43+
To find a peer, provide the flag `-p` with the [Peer ID] of the peer you're looking for:
44+
45+
46+
```console
47+
$ ./routing-client -e http://127.0.0.1:8080 -p 12D3KooWC9L4RjPGgqpzBUBkcVpKjJYofCkC5i5QdQftg1LdsFb2
48+
49+
12D3KooWC9L4RjPGgqpzBUBkcVpKjJYofCkC5i5QdQftg1LdsFb2
50+
Protocols: []
51+
Addresses: [/ip4/198.244.201.187/tcp/4001]
52+
```
53+
54+
### Get an IPNS Record
55+
56+
To find an IPNS record, provide the flag `-n` with the [IPNS Name] you're trying to find a record for:
57+
58+
```console
59+
$ ./routing-client -e http://127.0.0.1:8080 -n /ipns/k51qzi5uqu5diuz0h5tjqama8qbmyxusvqz2hfgn5go5l07l9k2ubqa09m7toe
60+
61+
/ipns/k51qzi5uqu5diuz0h5tjqama8qbmyxusvqz2hfgn5go5l07l9k2ubqa09m7toe
62+
Value: /ipfs/QmUGMoVz62ZARyxkrdEiwmFZanTwVWLLu6EAWvbWHNcwR8
63+
```
64+
65+
[Kubo]: https://github.com/ipfs/kubo
66+
[CID]: https://docs.ipfs.tech/concepts/content-addressing/#what-is-a-cid
67+
[Peer ID]: https://docs.libp2p.io/concepts/fundamentals/peers/#peer-id
68+
[IPNS Name]: https://specs.ipfs.tech/ipns/ipns-record/#ipns-name

examples/routing-v1/client/main.go

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"flag"
7+
"fmt"
8+
"log"
9+
"time"
10+
11+
"github.com/ipfs/boxo/ipns"
12+
"github.com/ipfs/boxo/routing/http/client"
13+
"github.com/ipfs/boxo/routing/http/types"
14+
"github.com/ipfs/boxo/routing/http/types/iter"
15+
"github.com/ipfs/go-cid"
16+
"github.com/libp2p/go-libp2p/core/peer"
17+
)
18+
19+
func main() {
20+
gatewayUrlPtr := flag.String("e", "", "routing v1 endpoint to use")
21+
timeoutPtr := flag.Int("t", 10, "timeout in seconds for lookup")
22+
cidPtr := flag.String("c", "", "cid to find")
23+
pidPtr := flag.String("p", "", "peer to find")
24+
namePtr := flag.String("n", "", "ipns name to retrieve record for")
25+
flag.Parse()
26+
27+
// Creates a new Delegated Routing V1 client.
28+
client, err := client.New(*gatewayUrlPtr)
29+
if err != nil {
30+
log.Fatal(err)
31+
}
32+
33+
timeout := time.Duration((*timeoutPtr)) * time.Second
34+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
35+
defer cancel()
36+
37+
if *cidPtr != "" {
38+
err = findProviders(ctx, client, *cidPtr)
39+
} else if *pidPtr != "" {
40+
err = findPeers(ctx, client, *pidPtr)
41+
} else if *namePtr != "" {
42+
err = findIPNS(ctx, client, *namePtr)
43+
} else {
44+
err = errors.New("cid or peer must be provided")
45+
}
46+
47+
if err != nil {
48+
log.Fatal(err)
49+
}
50+
}
51+
52+
func findProviders(ctx context.Context, client *client.Client, cidStr string) error {
53+
// Parses the given CID to lookup the providers for.
54+
contentCid, err := cid.Parse(cidStr)
55+
if err != nil {
56+
return err
57+
}
58+
59+
// Ask for providers providing the given content CID.
60+
recordsIter, err := client.FindProviders(ctx, contentCid)
61+
if err != nil {
62+
return err
63+
}
64+
defer recordsIter.Close()
65+
return printIter(recordsIter)
66+
}
67+
68+
func findPeers(ctx context.Context, client *client.Client, pidStr string) error {
69+
// Parses the given Peer ID to lookup the information for.
70+
pid, err := peer.Decode(pidStr)
71+
if err != nil {
72+
return err
73+
}
74+
75+
// Ask for information about the peer with the given peer ID.
76+
recordsIter, err := client.FindPeers(ctx, pid)
77+
if err != nil {
78+
return err
79+
}
80+
defer recordsIter.Close()
81+
return printIter(recordsIter)
82+
}
83+
84+
func printIter(iter iter.ResultIter[types.Record]) error {
85+
// The response is streamed. Alternatively, you could use [iter.ReadAll]
86+
// to fetch all the results all at once, instead of iterating as they are
87+
// streamed.
88+
for iter.Next() {
89+
res := iter.Val()
90+
91+
// Check for error, but do not complain if we exceeded the timeout. We are
92+
// expecting that to happen: we explicitly defined a timeout.
93+
if res.Err != nil {
94+
if !errors.Is(res.Err, context.DeadlineExceeded) {
95+
return res.Err
96+
}
97+
98+
return nil
99+
}
100+
101+
switch res.Val.GetSchema() {
102+
case types.SchemaPeer:
103+
record := res.Val.(*types.PeerRecord)
104+
fmt.Println(record.ID)
105+
fmt.Println("\tProtocols:", record.Protocols)
106+
fmt.Println("\tAddresses:", record.Addrs)
107+
default:
108+
// You may not want to fail here, it's up to you. You can just handle
109+
// the schemas you want, or that you know, but not fail.
110+
log.Printf("unrecognized schema: %s", res.Val.GetSchema())
111+
}
112+
}
113+
114+
return nil
115+
}
116+
117+
func findIPNS(ctx context.Context, client *client.Client, nameStr string) error {
118+
// Parses the given name string to get a record for.
119+
name, err := ipns.NameFromString(nameStr)
120+
if err != nil {
121+
return err
122+
}
123+
124+
// Ask for a record. [client.GetIPNS] validates the record for us.
125+
record, err := client.GetIPNS(ctx, name)
126+
if err != nil {
127+
return err
128+
}
129+
130+
v, err := record.Value()
131+
if err != nil {
132+
return err
133+
}
134+
135+
fmt.Printf("/ipns/%s\n", name)
136+
fmt.Println("\tValue:", v.String())
137+
return nil
138+
}

0 commit comments

Comments
 (0)