From a58f2a2820f89624ac7c8c5046860a70b138a658 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Wed, 4 Oct 2023 13:52:21 +0200 Subject: [PATCH] feat(examples): routing v1 client cli --- examples/README.md | 1 + examples/go.mod | 1 + examples/go.sum | 4 + examples/routing-v1/client/README.md | 68 +++++++++++ examples/routing-v1/client/main.go | 164 +++++++++++++++++++++++++++ 5 files changed, 238 insertions(+) create mode 100644 examples/routing-v1/client/README.md create mode 100644 examples/routing-v1/client/main.go diff --git a/examples/README.md b/examples/README.md index 1c63f087d1..4c496e6a66 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,3 +9,4 @@ Let us know if you find any issue or if you want to contribute and add a new tut - [Fetching a UnixFS file by CID](./unixfs-file-cid) - [Gateway backed by a CAR file](./gateway/car) - [Gateway backed by a remote blockstore and IPNS resolver](./gateway/proxy) +- [Delegated Routing V1 Command Line Client](./routing-v1/client/) diff --git a/examples/go.mod b/examples/go.mod index 790573a69f..24bcbe929d 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -125,6 +125,7 @@ require ( github.com/quic-go/quic-go v0.38.0 // indirect github.com/quic-go/webtransport-go v0.5.3 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect + github.com/samber/lo v1.36.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb // indirect github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect diff --git a/examples/go.sum b/examples/go.sum index eaec6f954e..93ee14dd35 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -216,6 +216,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw= +github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= 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 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb h1:Ywfo8sUltxogBpFuMOFRrrSifO788kAFxmvVw31PtQQ= github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb/go.mod h1:ikPs9bRWicNw3S7XpJ8sK/smGwU9WcSVU3dy9qahYBM= diff --git a/examples/routing-v1/client/README.md b/examples/routing-v1/client/README.md new file mode 100644 index 0000000000..e655ad13d8 --- /dev/null +++ b/examples/routing-v1/client/README.md @@ -0,0 +1,68 @@ +# Delegated Routing V1 Command Line Client + +This is an example of how to use the Delegated Routing V1 HTTP client from Boxo. +In this package, we build a small command line tool that allows you to connect to +a Routing V1 endpoint and fetch content providers, peer information, as well as +IPNS records for a certain IPNS name. + +## Build + +```bash +> go build -o routing-client +``` + +## Usage + +First, you will need a Routing V1 server. For that, you can potentially use [Kubo], +which supports [exposing](https://github.com/ipfs/kubo/blob/master/docs/config.md#gatewayexposeroutingapi) +a Routing V1 endpoint. For the commands below, we assume the endpoint is `http://127.0.0.1:8080`. + +### Find CID Providers + +To find providers, provide the flag `-c` with the [CID] of the content you're looking for: + +```console +$ ./routing-client -e http://127.0.0.1:8080 -c bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4 + +12D3KooWEfL19QqRGGLraaAYw1XA3dtDdVRYaHt6jymFxcuQo3Zm + Protocols: [] + Addresses: [/ip4/163.47.51.218/tcp/28131] +12D3KooWK53GAx2g2UUYfJHHjxDbVLeDgGxNMHXDWeJa5KgMhTD2 + Protocols: [] + Addresses: [/ip4/195.167.147.43/udp/8888/quic /ip4/195.167.147.43/tcp/8888] +12D3KooWCpr8kACTRLKrPy4LPpSX7LXvKQ7eYqTmY8CBvgK5HZgB + Protocols: [] + Addresses: [/ip4/163.47.49.234/tcp/28102] +12D3KooWC9L4RjPGgqpzBUBkcVpKjJYofCkC5i5QdQftg1LdsFb2 + Protocols: [] + Addresses: [/ip4/198.244.201.187/tcp/4001] +``` + +### Find Peer Information + +To find a peer, provide the flag `-p` with the [Peer ID] of the peer you're looking for: + + +```console +$ ./routing-client -e http://127.0.0.1:8080 -p 12D3KooWC9L4RjPGgqpzBUBkcVpKjJYofCkC5i5QdQftg1LdsFb2 + +12D3KooWC9L4RjPGgqpzBUBkcVpKjJYofCkC5i5QdQftg1LdsFb2 + Protocols: [] + Addresses: [/ip4/198.244.201.187/tcp/4001] +``` + +### Get an IPNS Record + +To find an IPNS record, provide the flag `-n` with the [IPNS Name] you're trying to find a record for: + +```console +$ ./routing-client -e http://127.0.0.1:8080 -n /ipns/k51qzi5uqu5diuz0h5tjqama8qbmyxusvqz2hfgn5go5l07l9k2ubqa09m7toe + +/ipns/k51qzi5uqu5diuz0h5tjqama8qbmyxusvqz2hfgn5go5l07l9k2ubqa09m7toe + Value: /ipfs/QmUGMoVz62ZARyxkrdEiwmFZanTwVWLLu6EAWvbWHNcwR8 +``` + +[Kubo]: https://github.com/ipfs/kubo +[CID]: https://docs.ipfs.tech/concepts/content-addressing/#what-is-a-cid +[Peer ID]: https://docs.libp2p.io/concepts/fundamentals/peers/#peer-id +[IPNS Name]: https://specs.ipfs.tech/ipns/ipns-record/#ipns-name diff --git a/examples/routing-v1/client/main.go b/examples/routing-v1/client/main.go new file mode 100644 index 0000000000..8de0ba8cdf --- /dev/null +++ b/examples/routing-v1/client/main.go @@ -0,0 +1,164 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "log" + "time" + + "github.com/ipfs/boxo/ipns" + "github.com/ipfs/boxo/routing/http/client" + "github.com/ipfs/boxo/routing/http/types" + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/peer" +) + +func main() { + gatewayUrlPtr := flag.String("e", "", "routing v1 endpoint to use") + timeoutPtr := flag.Int("t", 10, "timeout in seconds for lookup") + cidPtr := flag.String("c", "", "cid to find") + pidPtr := flag.String("p", "", "peer to find") + namePtr := flag.String("n", "", "ipns name to retrieve record for") + flag.Parse() + + // Creates a new Delegated Routing V1 client. + client, err := client.New(*gatewayUrlPtr) + if err != nil { + log.Fatal(err) + } + + timeout := time.Duration((*timeoutPtr)) * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + if *cidPtr != "" { + err = findProviders(ctx, client, *cidPtr) + } else if *pidPtr != "" { + err = findPeers(ctx, client, *pidPtr) + } else if *namePtr != "" { + err = findIPNS(ctx, client, *namePtr) + } else { + err = errors.New("cid or peer must be provided") + } + + if err != nil { + log.Fatal(err) + } +} + +func findProviders(ctx context.Context, client *client.Client, cidStr string) error { + // Parses the given CID to lookup the providers for. + contentCid, err := cid.Parse(cidStr) + if err != nil { + return err + } + + // Ask for providers providing the given content CID. + provIter, err := client.FindProviders(ctx, contentCid) + if err != nil { + return err + } + defer provIter.Close() + + // The response is streamed. Alternatively, you could use [iter.ReadAll] + // to fetch all the results all at once, instead of iterating as they are + // streamed. + for provIter.Next() { + res := provIter.Val() + + // Check for error, but do not complain if we exceeded the timeout. We are + // expecting that to happen: we explicitly defined a timeout. + if res.Err != nil { + if !errors.Is(res.Err, context.DeadlineExceeded) { + return res.Err + } + + return nil + } + + switch res.Val.GetSchema() { + case types.SchemaPeer: + record := res.Val.(*types.PeerRecord) + fmt.Println(record.ID) + fmt.Println("\tProtocols:", record.Protocols) + fmt.Println("\tAddresses:", record.Addrs) + default: + // You may not want to fail here, it's up to you. You can just handle + // the schemas you want, or that you know, but not fail. + log.Printf("unrecognized schema: %s", res.Val.GetSchema()) + } + } + + return nil +} + +func findPeers(ctx context.Context, client *client.Client, pidStr string) error { + // Parses the given Peer ID to lookup the information for. + pid, err := peer.Decode(pidStr) + if err != nil { + return err + } + + // Ask for information about the peer with the given peer ID. + provIter, err := client.FindPeers(ctx, pid) + if err != nil { + return err + } + defer provIter.Close() + + // The response is streamed. Alternatively, you could use [iter.ReadAll] + // to fetch all the results all at once, instead of iterating as they are + // streamed. + for provIter.Next() { + res := provIter.Val() + + // Check for error, but do not complain if we exceeded the timeout. We are + // expecting that to happen: we explicitly defined a timeout. + if res.Err != nil { + if !errors.Is(res.Err, context.DeadlineExceeded) { + return res.Err + } + + return nil + } + + switch res.Val.GetSchema() { + case types.SchemaPeer: + record := res.Val.(*types.PeerRecord) + fmt.Println(record.ID) + fmt.Println("\tProtocols:", record.Protocols) + fmt.Println("\tAddresses:", record.Addrs) + default: + // You may not want to fail here, it's up to you. You can just handle + // the schemas you want, or that you know, but not fail. + log.Printf("unrecognized schema: %s", res.Val.GetSchema()) + } + } + + return nil +} + +func findIPNS(ctx context.Context, client *client.Client, nameStr string) error { + // Parses the given name string to get a record for. + name, err := ipns.NameFromString(nameStr) + if err != nil { + return err + } + + // Ask for a record. [client.GetIPNS] validates the record for us. + record, err := client.GetIPNS(ctx, name) + if err != nil { + return err + } + + v, err := record.Value() + if err != nil { + return err + } + + fmt.Printf("/ipns/%s\n", name) + fmt.Println("\tValue:", v.String()) + return nil +}