Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrate subdomain and dnslink code #153

Merged
merged 2 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions examples/gateway/car/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Gateway backed by a CAR File
# HTTP Gateway backed by a CAR File

This is an example that shows how to build a Gateway backed by the contents of
a CAR file. A [CAR file](https://ipld.io/specs/transport/car/) is a Content
Expand All @@ -7,7 +7,7 @@ Addressable aRchive that contains blocks.
## Build

```bash
> go build -o gateway
> go build -o car-gateway
```

## Usage
Expand All @@ -23,10 +23,19 @@ Then, you can start the gateway with:


```
./gateway -c data.car -p 8040
./car-gateway -c data.car -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
### Subdomain gateway

Now you can access the gateway in [localhost:8040](http://localhost:8040). It will
behave like a regular [Subdomain IPFS Gateway](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#subdomain-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.

### Path gateway

If you don't need Origin isolation and only care about hosting flat files,
a plain [path gateway](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#path-gateway) at [127.0.0.1:8040](http://127.0.0.1:8040)
may suffice.
34 changes: 27 additions & 7 deletions examples/gateway/car/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import (
"github.com/ipfs/go-cid"
offline "github.com/ipfs/go-ipfs-exchange-offline"
"github.com/ipfs/go-libipfs/examples/gateway/common"
"github.com/ipfs/go-libipfs/gateway"
carblockstore "github.com/ipld/go-car/v2/blockstore"
)

func main() {
carFilePtr := flag.String("c", "", "path to CAR file to back this gateway from")
portPtr := flag.Int("p", 8080, "port to run this gateway from")
port := flag.Int("p", 8040, "port to run this gateway from")
flag.Parse()

blockService, roots, f, err := newBlockServiceFromCAR(*carFilePtr)
Expand All @@ -26,20 +27,39 @@ func main() {
}
defer f.Close()

gateway, err := common.NewBlocksGateway(blockService, nil)
gwAPI, err := common.NewBlocksGateway(blockService, nil)
if err != nil {
log.Fatal(err)
}
handler := common.NewBlocksHandler(gwAPI, *port)

handler := common.NewBlocksHandler(gateway, *portPtr)
// Initialize the public gateways that we will want to have available through
// Host header rewritting. This step is optional and only required if you're
// running multiple public gateways and want different settings and support
// for DNSLink and Subdomain Gateways.
noDNSLink := true // If you set DNSLink to point at the CID from CAR, you can load it!
publicGateways := map[string]*gateway.Specification{
// Support public requests with Host: CID.ipfs.example.net and ID.ipns.example.net
"example.net": {
Paths: []string{"/ipfs", "/ipns"},
NoDNSLink: noDNSLink,
UseSubdomains: true,
},
// Support local requests
"localhost": {
Paths: []string{"/ipfs", "/ipns"},
NoDNSLink: noDNSLink,
UseSubdomains: true,
},
}
handler = gateway.WithHostname(handler, gwAPI, publicGateways, noDNSLink)

address := "127.0.0.1:" + strconv.Itoa(*portPtr)
log.Printf("Listening on http://%s", address)
log.Printf("Listening on http://localhost:%d", *port)
for _, cid := range roots {
log.Printf("Hosting CAR root at http://%s/ipfs/%s", address, cid.String())
log.Printf("Hosting CAR root at http://localhost:%d/ipfs/%s", *port, cid.String())
}

if err := http.ListenAndServe(address, handler); err != nil {
if err := http.ListenAndServe(":"+strconv.Itoa(*port), handler); err != nil {
log.Fatal(err)
}
}
Expand Down
14 changes: 14 additions & 0 deletions examples/gateway/common/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package common

import (
"context"
"errors"
"fmt"
"net/http"
gopath "path"
Expand All @@ -24,6 +25,7 @@ import (
uio "github.com/ipfs/go-unixfs/io"
"github.com/ipfs/go-unixfsnode"
iface "github.com/ipfs/interface-go-ipfs-core"
nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys"
ifacepath "github.com/ipfs/interface-go-ipfs-core/path"
dagpb "github.com/ipld/go-codec-dagpb"
"github.com/ipld/go-ipld-prime"
Expand Down Expand Up @@ -143,6 +145,18 @@ func (api *BlocksGateway) GetIPNSRecord(ctx context.Context, c cid.Cid) ([]byte,
return nil, routing.ErrNotSupported
}

func (api *BlocksGateway) GetDNSLinkRecord(ctx context.Context, hostname string) (ifacepath.Path, error) {
if api.namesys != nil {
p, err := api.namesys.Resolve(ctx, "/ipns/"+hostname, nsopts.Depth(1))
if err == namesys.ErrResolveRecursion {
err = nil
}
return ifacepath.New(p.String()), err
}

return nil, errors.New("not implemented")
}

func (api *BlocksGateway) IsCached(ctx context.Context, p ifacepath.Path) bool {
rp, err := api.ResolvePath(ctx, p)
if err != nil {
Expand Down
28 changes: 25 additions & 3 deletions examples/gateway/proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,31 @@ types. Once you have it, run the proxy gateway with its address as the host para


```
./verifying-proxy -h https://ipfs.io -p 8040
./verifying-proxy -g https://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 it runs no libp2p, and has no local blockstore.
### Subdomain gateway

Now you can access the gateway in [localhost:8040](http://localhost:8040). It will
behave like a regular [Subdomain IPFS Gateway](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#subdomain-gateway),
except for the fact that it runs no libp2p, and has no local blockstore.
All contents are provided by a remote gateway and fetched as RAW Blocks and Records, and verified locally.

### Path gateway

If you don't need Origin isolation and only care about hosting flat files,
a plain [path gateway](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#path-gateway) at [127.0.0.1:8040](http://127.0.0.1:8040)
may suffice.

### DNSLink gateway

Gateway supports hosting of [DNSLink](https://dnslink.dev/) websites. All you need is to pass `Host` header with FQDN that has DNSLink set up:

```console
$ curl -sH 'Host: en.wikipedia-on-ipfs.org' 'http://127.0.0.1:8080/wiki/' | head -3
<!DOCTYPE html><html class="client-js"><head>
<meta charset="UTF-8">
<title>Wikipedia, the free encyclopedia</title>
```

Put it behind a reverse proxy terminating TLS (like Nginx) and voila!
34 changes: 27 additions & 7 deletions examples/gateway/proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (
"github.com/ipfs/go-blockservice"
offline "github.com/ipfs/go-ipfs-exchange-offline"
"github.com/ipfs/go-libipfs/examples/gateway/common"
"github.com/ipfs/go-libipfs/gateway"
)

func main() {
gatewayUrlPtr := flag.String("g", "", "gateway to proxy to")
portPtr := flag.Int("p", 8080, "port to run this gateway from")
port := flag.Int("p", 8040, "port to run this gateway from")
flag.Parse()

// Sets up the block store, which will proxy the block requests to the given gateway.
Expand All @@ -24,16 +25,35 @@ func main() {
routing := newProxyRouting(*gatewayUrlPtr, nil)

// Creates the gateway with the block service and the routing.
gateway, err := common.NewBlocksGateway(blockService, routing)
gwAPI, err := common.NewBlocksGateway(blockService, routing)
if err != nil {
log.Fatal(err)
}
handler := common.NewBlocksHandler(gwAPI, *port)

// Initialize the public gateways that we will want to have available through
// Host header rewritting. This step is optional and only required if you're
// running multiple public gateways and want different settings and support
// for DNSLink and Subdomain Gateways.
noDNSLink := false
publicGateways := map[string]*gateway.Specification{
// Support public requests with Host: CID.ipfs.example.net and ID.ipns.example.net
"example.net": {
Paths: []string{"/ipfs", "/ipns"},
NoDNSLink: noDNSLink,
UseSubdomains: true,
},
// Support local requests
"localhost": {
Paths: []string{"/ipfs", "/ipns"},
NoDNSLink: noDNSLink,
UseSubdomains: true,
},
}
handler = gateway.WithHostname(handler, gwAPI, publicGateways, noDNSLink)

handler := common.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.Printf("Listening on http://localhost:%d", *port)
if err := http.ListenAndServe(":"+strconv.Itoa(*port), handler); err != nil {
log.Fatal(err)
}
}
6 changes: 6 additions & 0 deletions gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ type API interface {
// from the routing system.
GetIPNSRecord(context.Context, cid.Cid) ([]byte, error)

// GetDNSLinkRecord returns the DNSLink TXT record for the provided FQDN.
// Unlike ResolvePath, it does not perform recursive resolution. It only
// checks for the existence of a DNSLink TXT record with path starting with
// /ipfs/ or /ipns/ and returns the path as-is.
GetDNSLinkRecord(context.Context, string) (path.Path, error)

// IsCached returns whether or not the path exists locally.
IsCached(context.Context, path.Path) bool

Expand Down
103 changes: 103 additions & 0 deletions gateway/gateway_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package gateway

import (
"context"
"errors"
"strings"

cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-libipfs/blocks"
"github.com/ipfs/go-libipfs/files"
"github.com/ipfs/go-namesys"
path "github.com/ipfs/go-path"
iface "github.com/ipfs/interface-go-ipfs-core"
nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
"github.com/libp2p/go-libp2p/core/crypto"
)

type mockNamesys map[string]path.Path

func (m mockNamesys) Resolve(ctx context.Context, name string, opts ...nsopts.ResolveOpt) (value path.Path, err error) {
cfg := nsopts.DefaultResolveOpts()
for _, o := range opts {
o(&cfg)
}
depth := cfg.Depth
if depth == nsopts.UnlimitedDepth {
// max uint
depth = ^uint(0)
}
for strings.HasPrefix(name, "/ipns/") {
if depth == 0 {
return value, namesys.ErrResolveRecursion
}
depth--

var ok bool
value, ok = m[name]
if !ok {
return "", namesys.ErrResolveFailed
}
name = value.String()
}
return value, nil
}

func (m mockNamesys) ResolveAsync(ctx context.Context, name string, opts ...nsopts.ResolveOpt) <-chan namesys.Result {
out := make(chan namesys.Result, 1)
v, err := m.Resolve(ctx, name, opts...)
out <- namesys.Result{Path: v, Err: err}
close(out)
return out
}

func (m mockNamesys) Publish(ctx context.Context, name crypto.PrivKey, value path.Path, opts ...nsopts.PublishOption) error {
return errors.New("not implemented for mockNamesys")
}

func (m mockNamesys) GetResolver(subs string) (namesys.Resolver, bool) {
return nil, false
}

type mockApi struct {
ns mockNamesys
}

func newMockApi() *mockApi {
return &mockApi{
ns: mockNamesys{},
}
}

func (m *mockApi) GetUnixFsNode(context.Context, ipath.Resolved) (files.Node, error) {
return nil, errors.New("not implemented")
}

func (m *mockApi) LsUnixFsDir(context.Context, ipath.Resolved) (<-chan iface.DirEntry, error) {
return nil, errors.New("not implemented")
}

func (m *mockApi) GetBlock(context.Context, cid.Cid) (blocks.Block, error) {
return nil, errors.New("not implemented")
}

func (m *mockApi) GetIPNSRecord(context.Context, cid.Cid) ([]byte, error) {
return nil, errors.New("not implemented")
}

func (m *mockApi) GetDNSLinkRecord(ctx context.Context, hostname string) (ipath.Path, error) {
p, err := m.ns.Resolve(ctx, "/ipns/"+hostname, nsopts.Depth(1))
if err == namesys.ErrResolveRecursion {
err = nil
}
return ipath.New(p.String()), err
}

func (m *mockApi) IsCached(context.Context, ipath.Path) bool {
return false
}

func (m *mockApi) ResolvePath(context.Context, ipath.Path) (ipath.Resolved, error) {
return nil, errors.New("not implemented")
}
Loading