Skip to content

Commit

Permalink
docs: example proxy gatewya to ?format=raw & ?format=ipns-record
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed Feb 2, 2023
1 parent 5123c65 commit 31241d6
Show file tree
Hide file tree
Showing 10 changed files with 462 additions and 46 deletions.
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Let us know if you find any issue or if you want to contribute and add a new tut
## Examples and Tutorials

- [Gateway backed by a CAR file](./gateway/car)
- [Gateway that proxies block and IPNS requests](./gateway/proxy)
58 changes: 52 additions & 6 deletions examples/gateway/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package gateway

import (
"context"
"errors"
"fmt"
"net/http"
gopath "path"

"github.com/ipfs/go-blockservice"
Expand All @@ -13,7 +13,10 @@ import (
format "github.com/ipfs/go-ipld-format"
"github.com/ipfs/go-libipfs/blocks"
"github.com/ipfs/go-libipfs/files"
"github.com/ipfs/go-libipfs/gateway"
"github.com/ipfs/go-merkledag"
"github.com/ipfs/go-namesys"
"github.com/ipfs/go-namesys/resolve"
ipfspath "github.com/ipfs/go-path"
"github.com/ipfs/go-path/resolver"
"github.com/ipfs/go-unixfs"
Expand All @@ -26,16 +29,36 @@ import (
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ipld/go-ipld-prime/schema"
"github.com/libp2p/go-libp2p/core/routing"
)

func NewBlocksHandler(gw *BlocksGateway, port int) http.Handler {
headers := map[string][]string{}
gateway.AddAccessControlHeaders(headers)

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

mux := http.NewServeMux()
gwHandler := gateway.NewHandler(conf, gw)
mux.Handle("/ipfs/", gwHandler)
mux.Handle("/ipns/", gwHandler)
return mux
}

type BlocksGateway struct {
blockStore blockstore.Blockstore
blockService blockservice.BlockService
dagService format.DAGService
resolver resolver.Resolver

// Optional routing system to handle /ipns addresses.
namesys namesys.NameSystem
routing routing.ValueStore
}

func NewBlocksGateway(blockService blockservice.BlockService) (*BlocksGateway, error) {
func NewBlocksGateway(blockService blockservice.BlockService, routing routing.ValueStore) (*BlocksGateway, error) {
// Setup the DAG services, which use the CAR block store.
dagService := merkledag.NewDAGService(blockService)

Expand All @@ -50,11 +73,25 @@ func NewBlocksGateway(blockService blockservice.BlockService) (*BlocksGateway, e
fetcher := fetcherConfig.WithReifier(unixfsnode.Reify)
resolver := resolver.NewBasicResolver(fetcher)

// Setup a name system so that we are able to resolve /ipns links.
var (
ns namesys.NameSystem
err error
)
if routing != nil {
ns, err = namesys.NewNameSystem(routing)
if err != nil {
return nil, err
}
}

return &BlocksGateway{
blockStore: blockService.Blockstore(),
blockService: blockService,
dagService: dagService,
resolver: resolver,
routing: routing,
namesys: ns,
}, nil
}

Expand Down Expand Up @@ -98,8 +135,12 @@ func (api *BlocksGateway) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block
return api.blockService.GetBlock(ctx, c)
}

func (api *BlocksGateway) GetIPNSRecord(context.Context, cid.Cid) ([]byte, error) {
return nil, errors.New("not implemented")
func (api *BlocksGateway) GetIPNSRecord(ctx context.Context, c cid.Cid) ([]byte, error) {
if api.routing != nil {
return api.routing.GetValue(ctx, "/ipns/"+c.String())
}

return nil, routing.ErrNotSupported
}

func (api *BlocksGateway) IsCached(ctx context.Context, p ifacepath.Path) bool {
Expand All @@ -121,11 +162,16 @@ func (api *BlocksGateway) ResolvePath(ctx context.Context, p ifacepath.Path) (if
return nil, err
}

if p.Namespace() != "ipfs" {
ipath := ipfspath.Path(p.String())
ipath, err := resolve.ResolveIPNS(ctx, api.namesys, ipath)
if err != nil {
return nil, err
}

if ipath.Segments()[0] != "ipfs" {
return nil, fmt.Errorf("unsupported path namespace: %s", p.Namespace())
}

ipath := ipfspath.Path(p.String())
node, rest, err := api.resolver.ResolveToLastNode(ctx, ipath)
if err != nil {
return nil, err
Expand Down
20 changes: 2 additions & 18 deletions examples/gateway/car/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/ipfs/go-cid"
offline "github.com/ipfs/go-ipfs-exchange-offline"
gw "github.com/ipfs/go-libipfs/examples/gateway"
"github.com/ipfs/go-libipfs/gateway"
carblockstore "github.com/ipld/go-car/v2/blockstore"
)

Expand All @@ -27,12 +26,12 @@ func main() {
}
defer f.Close()

gateway, err := gw.NewBlocksGateway(blockService)
gateway, err := gw.NewBlocksGateway(blockService, nil)
if err != nil {
log.Fatal(err)
}

handler := newHandler(gateway, *portPtr)
handler := gw.NewBlocksHandler(gateway, *portPtr)

address := "127.0.0.1:" + strconv.Itoa(*portPtr)
log.Printf("Listening on http://%s", address)
Expand Down Expand Up @@ -65,18 +64,3 @@ func newBlockServiceFromCAR(filepath string) (blockservice.BlockService, []cid.C
blockService := blockservice.New(bs, offline.Exchange(bs))
return blockService, roots, r, nil
}

func newHandler(gw *gw.BlocksGateway, port int) http.Handler {
headers := map[string][]string{}
gateway.AddAccessControlHeaders(headers)

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

mux := http.NewServeMux()
gwHandler := gateway.NewHandler(conf, gw)
mux.Handle("/ipfs/", gwHandler)
mux.Handle("/ipns/", gwHandler)
return mux
}
4 changes: 2 additions & 2 deletions examples/gateway/car/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ func newTestServer() (*httptest.Server, io.Closer, error) {
return nil, nil, err
}

gateway, err := gw.NewBlocksGateway(blockService)
gateway, err := gw.NewBlocksGateway(blockService, nil)
if err != nil {
_ = f.Close()
return nil, nil, err
}

handler := newHandler(gateway, 0)
handler := gw.NewBlocksHandler(gateway, 0)
ts := httptest.NewServer(handler)
return ts, f, nil
}
Expand Down
30 changes: 30 additions & 0 deletions examples/gateway/proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Proxy Gateway

This is an example that shows how to build a Gateway backed by the contents
of another gateway. In this example, we implement two major structures:

- [Block Store](./blockstore.go), which forwards the block requests to the backend
gateway using `?format=raw`, and
- [Routing System](./routing.go), which forwards the IPNS requests to the backend
gateway using `?format=ipns-record`. In addition, DNSLink lookups are done locally.

## Build

```bash
> go build -o proxy
```

## Usage

First, you need a compliant gateway that supports both RAW and IPNS Record response
types. Once you have it, run the proxy gateway with its address as the host parameter:


```
./proxy -h ipfs.io -p 8040
```

Now you can access the gateway in [127.0.0.1:8040](http://127.0.0.1:8040). It will
behave like a regular IPFS Gateway, except for the fact that all contents are provided
from the CAR file. Therefore, things such as IPNS resolution and fetching contents
from nodes in the IPFS network won't work.
111 changes: 111 additions & 0 deletions examples/gateway/proxy/blockstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package main

import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"

"github.com/ipfs/go-cid"
blockstore "github.com/ipfs/go-ipfs-blockstore"
"github.com/ipfs/go-libipfs/blocks"
)

var (
errNotImplemented = errors.New("not implemented")
)

type proxyStore struct {
httpClient *http.Client
host string
validate bool
}

func newProxyStore(host string, client *http.Client) blockstore.Blockstore {
if client == nil {
client = http.DefaultClient
}

return &proxyStore{
host: host,
httpClient: client,
}
}

func (ps *proxyStore) fetch(ctx context.Context, c cid.Cid) (blocks.Block, error) {
u, err := url.Parse(fmt.Sprintf("http://%s/ipfs/%s?format=raw", ps.host, c))
if err != nil {
return nil, err
}
resp, err := ps.httpClient.Do(&http.Request{
Method: http.MethodGet,
URL: u,
Header: http.Header{
"Accept": []string{"application/vnd.ipld.raw"},
},
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
rb, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if ps.validate {
nc, err := c.Prefix().Sum(rb)
if err != nil {
return nil, blocks.ErrWrongHash
}
if !nc.Equals(c) {
fmt.Printf("got %s vs %s\n", nc, c)
return nil, blocks.ErrWrongHash
}
}
return blocks.NewBlockWithCid(rb, c)
}

func (ps *proxyStore) Has(ctx context.Context, c cid.Cid) (bool, error) {
blk, err := ps.fetch(ctx, c)
if err != nil {
return false, err
}
return blk != nil, nil
}

func (ps *proxyStore) Get(ctx context.Context, c cid.Cid) (blocks.Block, error) {
blk, err := ps.fetch(ctx, c)
if err != nil {
return nil, err
}
return blk, nil
}

func (ps *proxyStore) GetSize(ctx context.Context, c cid.Cid) (int, error) {
blk, err := ps.fetch(ctx, c)
if err != nil {
return 0, err
}
return len(blk.RawData()), nil
}

func (ps *proxyStore) HashOnRead(enabled bool) {
ps.validate = enabled
}

func (c *proxyStore) Put(context.Context, blocks.Block) error {
return errNotImplemented
}

func (c *proxyStore) PutMany(context.Context, []blocks.Block) error {
return errNotImplemented
}
func (c *proxyStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) {
return nil, errNotImplemented
}
func (c *proxyStore) DeleteBlock(context.Context, cid.Cid) error {
return errNotImplemented
}
39 changes: 39 additions & 0 deletions examples/gateway/proxy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"flag"
"log"
"net/http"
"strconv"

"github.com/ipfs/go-blockservice"
offline "github.com/ipfs/go-ipfs-exchange-offline"
gw "github.com/ipfs/go-libipfs/examples/gateway"
)

func main() {
hostPtr := flag.String("h", "", "hostname of gateway to proxy to")
portPtr := flag.Int("p", 8080, "port to run this gateway from")
flag.Parse()

// Sets up the block store, which will proxy the block requests to the given gateway.
blockStore := newProxyStore(*hostPtr, nil)
blockService := blockservice.New(blockStore, offline.Exchange(blockStore))

// Sets up the routing system, which will proxy the IPNS routing requests to the given gateway.
routing := newProxyRouting(*hostPtr, nil)

// Creates the gateway with the block service and the routing.
gateway, err := gw.NewBlocksGateway(blockService, routing)
if err != nil {
log.Fatal(err)
}

handler := gw.NewBlocksHandler(gateway, *portPtr)
address := "127.0.0.1:" + strconv.Itoa(*portPtr)
log.Printf("Listening on http://%s", address)

if err := http.ListenAndServe(address, handler); err != nil {
log.Fatal(err)
}
}
Loading

0 comments on commit 31241d6

Please sign in to comment.