Skip to content

Commit 5544107

Browse files
committedDec 8, 2022
feat(gateway): IPNS record response format
1 parent 6671993 commit 5544107

File tree

13 files changed

+216
-110
lines changed

13 files changed

+216
-110
lines changed
 

‎core/commands/routing.go

+16-102
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package commands
22

33
import (
44
"context"
5-
"encoding/base64"
65
"errors"
76
"fmt"
87
"io"
@@ -366,71 +365,25 @@ Different key types can specify other 'best' rules.
366365
cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
367366
},
368367
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
369-
nd, err := cmdenv.GetNode(env)
368+
api, err := cmdenv.GetApi(env, req)
370369
if err != nil {
371370
return err
372371
}
373372

374-
if !nd.IsOnline {
375-
return ErrNotOnline
376-
}
377-
378-
dhtkey, err := escapeDhtKey(req.Arguments[0])
373+
r, err := api.Routing().Get(req.Context, req.Arguments[0])
379374
if err != nil {
380375
return err
381376
}
382377

383-
ctx, cancel := context.WithCancel(req.Context)
384-
ctx, events := routing.RegisterForQueryEvents(ctx)
385-
386-
var getErr error
387-
go func() {
388-
defer cancel()
389-
var val []byte
390-
val, getErr = nd.Routing.GetValue(ctx, dhtkey)
391-
if getErr != nil {
392-
routing.PublishQueryEvent(ctx, &routing.QueryEvent{
393-
Type: routing.QueryError,
394-
Extra: getErr.Error(),
395-
})
396-
} else {
397-
routing.PublishQueryEvent(ctx, &routing.QueryEvent{
398-
Type: routing.Value,
399-
Extra: base64.StdEncoding.EncodeToString(val),
400-
})
401-
}
402-
}()
403-
404-
for e := range events {
405-
if err := res.Emit(e); err != nil {
406-
return err
407-
}
408-
}
409-
410-
return getErr
378+
return res.Emit(r)
411379
},
412380
Encoders: cmds.EncoderMap{
413-
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
414-
pfm := pfuncMap{
415-
routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
416-
if verbose {
417-
_, err := fmt.Fprintf(out, "got value: '%s'\n", obj.Extra)
418-
return err
419-
}
420-
res, err := base64.StdEncoding.DecodeString(obj.Extra)
421-
if err != nil {
422-
return err
423-
}
424-
_, err = out.Write(res)
425-
return err
426-
},
427-
}
428-
429-
verbose, _ := req.Options[dhtVerboseOptionName].(bool)
430-
return printEvent(out, w, verbose, pfm)
381+
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out []byte) error {
382+
_, err := w.Write(out)
383+
return err
431384
}),
432385
},
433-
Type: routing.QueryEvent{},
386+
Type: []byte{},
434387
}
435388

436389
var putValueRoutingCmd = &cmds.Command{
@@ -463,16 +416,7 @@ identified by QmFoo.
463416
cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
464417
},
465418
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
466-
nd, err := cmdenv.GetNode(env)
467-
if err != nil {
468-
return err
469-
}
470-
471-
if !nd.IsOnline {
472-
return ErrNotOnline
473-
}
474-
475-
key, err := escapeDhtKey(req.Arguments[0])
419+
api, err := cmdenv.GetApi(env, req)
476420
if err != nil {
477421
return err
478422
}
@@ -488,50 +432,20 @@ identified by QmFoo.
488432
return err
489433
}
490434

491-
ctx, cancel := context.WithCancel(req.Context)
492-
ctx, events := routing.RegisterForQueryEvents(ctx)
493-
494-
var putErr error
495-
go func() {
496-
defer cancel()
497-
putErr = nd.Routing.PutValue(ctx, key, []byte(data))
498-
if putErr != nil {
499-
routing.PublishQueryEvent(ctx, &routing.QueryEvent{
500-
Type: routing.QueryError,
501-
Extra: putErr.Error(),
502-
})
503-
}
504-
}()
505-
506-
for e := range events {
507-
if err := res.Emit(e); err != nil {
508-
return err
509-
}
435+
err = api.Routing().Put(req.Context, req.Arguments[0], data)
436+
if err != nil {
437+
return err
510438
}
511439

512-
return putErr
440+
return res.Emit([]byte(fmt.Sprintf("%s added", req.Arguments[0])))
513441
},
514442
Encoders: cmds.EncoderMap{
515-
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
516-
pfm := pfuncMap{
517-
routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
518-
if verbose {
519-
fmt.Fprintf(out, "* closest peer %s\n", obj.ID)
520-
}
521-
return nil
522-
},
523-
routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
524-
fmt.Fprintf(out, "%s\n", obj.ID.Pretty())
525-
return nil
526-
},
527-
}
528-
529-
verbose, _ := req.Options[dhtVerboseOptionName].(bool)
530-
531-
return printEvent(out, w, verbose, pfm)
443+
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out []byte) error {
444+
_, err := w.Write(out)
445+
return err
532446
}),
533447
},
534-
Type: routing.QueryEvent{},
448+
Type: []byte{},
535449
}
536450

537451
type printFunc func(obj *routing.QueryEvent, out io.Writer, verbose bool) error

‎core/coreapi/coreapi.go

+5
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ func (api *CoreAPI) PubSub() coreiface.PubSubAPI {
144144
return (*PubSubAPI)(api)
145145
}
146146

147+
// Routing returns the RoutingAPI interface implementation backed by the kubo node
148+
func (api *CoreAPI) Routing() coreiface.RoutingAPI {
149+
return (*RoutingAPI)(api)
150+
}
151+
147152
// WithOptions returns api with global options applied
148153
func (api *CoreAPI) WithOptions(opts ...options.ApiOption) (coreiface.CoreAPI, error) {
149154
settings := api.parentOpts // make sure to copy

‎core/coreapi/routing.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package coreapi
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"github.com/ipfs/go-path"
8+
coreiface "github.com/ipfs/interface-go-ipfs-core"
9+
peer "github.com/libp2p/go-libp2p/core/peer"
10+
)
11+
12+
type RoutingAPI CoreAPI
13+
14+
func (r *RoutingAPI) Get(ctx context.Context, key string) ([]byte, error) {
15+
if !r.nd.IsOnline {
16+
return nil, coreiface.ErrOffline
17+
}
18+
19+
dhtKey, err := normalizeKey(key)
20+
if err != nil {
21+
return nil, err
22+
}
23+
24+
return r.routing.GetValue(ctx, dhtKey)
25+
}
26+
27+
func (r *RoutingAPI) Put(ctx context.Context, key string, value []byte) error {
28+
if !r.nd.IsOnline {
29+
return coreiface.ErrOffline
30+
}
31+
32+
dhtKey, err := normalizeKey(key)
33+
if err != nil {
34+
return err
35+
}
36+
37+
return r.routing.PutValue(ctx, dhtKey, value)
38+
}
39+
40+
func normalizeKey(s string) (string, error) {
41+
parts := path.SplitList(s)
42+
if len(parts) != 3 ||
43+
parts[0] != "" ||
44+
!(parts[1] == "ipns" || parts[1] == "pk") {
45+
return "", errors.New("invalid key")
46+
}
47+
48+
k, err := peer.Decode(parts[2])
49+
if err != nil {
50+
return "", err
51+
}
52+
return path.Join(append(parts[:2], string(k))), nil
53+
}

‎core/corehttp/gateway.go

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ type NodeAPI interface {
3434
// Dag returns an implementation of Dag API
3535
Dag() coreiface.APIDagService
3636

37+
// Routing returns an implementation of Routing API.
38+
// Used for returning signed IPNS records, see IPIP-0328
39+
Routing() coreiface.RoutingAPI
40+
3741
// ResolvePath resolves the path using Unixfs resolver
3842
ResolvePath(context.Context, path.Path) (path.Resolved, error)
3943
}

‎core/corehttp/gateway_handler.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,9 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
445445
"application/cbor", "application/vnd.ipld.dag-cbor":
446446
logger.Debugw("serving codec", "path", contentPath)
447447
i.serveCodec(r.Context(), w, r, resolvedPath, contentPath, begin, responseFormat)
448+
case "application/vnd.ipfs.ipns-record":
449+
logger.Debugw("serving ipns record", "path", contentPath)
450+
i.serveIpnsRecord(r.Context(), w, r, resolvedPath, contentPath, begin, logger)
448451
return
449452
default: // catch-all for unsuported application/vnd.*
450453
err := fmt.Errorf("unsupported format %q", responseFormat)
@@ -886,6 +889,8 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string]
886889
return "application/vnd.ipld.dag-cbor", nil, nil
887890
case "cbor":
888891
return "application/cbor", nil, nil
892+
case "ipns-record":
893+
return "application/vnd.ipfs.ipns-record", nil, nil
889894
}
890895
}
891896
// Browsers and other user agents will send Accept header with generic types like:
@@ -896,7 +901,8 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string]
896901
if strings.HasPrefix(accept, "application/vnd.ipld") ||
897902
strings.HasPrefix(accept, "application/x-tar") ||
898903
strings.HasPrefix(accept, "application/json") ||
899-
strings.HasPrefix(accept, "application/cbor") {
904+
strings.HasPrefix(accept, "application/cbor") ||
905+
strings.HasPrefix(accept, "application/vnd.ipfs") {
900906
mediatype, params, err := mime.ParseMediaType(accept)
901907
if err != nil {
902908
return "", nil, err
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package corehttp
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"net/http"
8+
"strings"
9+
"time"
10+
11+
"github.com/gogo/protobuf/proto"
12+
ipns_pb "github.com/ipfs/go-ipns/pb"
13+
path "github.com/ipfs/go-path"
14+
ipath "github.com/ipfs/interface-go-ipfs-core/path"
15+
"go.uber.org/zap"
16+
)
17+
18+
func (i *gatewayHandler) serveIpnsRecord(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) {
19+
if contentPath.Namespace() != "ipns" {
20+
err := fmt.Errorf("%s is not an IPNS link", contentPath.String())
21+
webError(w, err.Error(), err, http.StatusBadRequest)
22+
return
23+
}
24+
25+
key := contentPath.String()
26+
key = strings.TrimSuffix(key, "/")
27+
if strings.Count(key, "/") > 2 {
28+
err := errors.New("cannot find ipns key for subpath")
29+
webError(w, err.Error(), err, http.StatusBadRequest)
30+
return
31+
}
32+
33+
rawRecord, err := i.api.Routing().Get(ctx, key)
34+
if err != nil {
35+
webError(w, err.Error(), err, http.StatusInternalServerError)
36+
return
37+
}
38+
39+
var record ipns_pb.IpnsEntry
40+
err = proto.Unmarshal(rawRecord, &record)
41+
if err != nil {
42+
webError(w, err.Error(), err, http.StatusInternalServerError)
43+
return
44+
}
45+
46+
// Set cache control headers based on the TTL set in the IPNS record. If the
47+
// TTL is not present, we use the Last-Modified tag. We are tracking IPNS
48+
// caching on: https://github.com/ipfs/kubo/issues/1818.
49+
// TODO: use addCacheControlHeaders once #1818 is fixed.
50+
w.Header().Set("Etag", getEtag(r, resolvedPath.Cid()))
51+
if record.Ttl != nil {
52+
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, must-revalidate", *record.Ttl))
53+
} else {
54+
w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
55+
}
56+
57+
// Set Content-Disposition
58+
var name string
59+
if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" {
60+
name = urlFilename
61+
} else {
62+
name = path.SplitList(key)[2] + ".ipns-record"
63+
}
64+
setContentDispositionHeader(w, name, "attachment")
65+
66+
w.Header().Set("Content-Type", "application/vnd.ipfs.ipns-record")
67+
w.Header().Set("X-Content-Type-Options", "nosniff")
68+
69+
_, _ = w.Write(rawRecord)
70+
}

‎docs/examples/kubo-as-a-library/go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ replace github.com/ipfs/kubo => ./../../..
88

99
require (
1010
github.com/ipfs/go-ipfs-files v0.2.0
11-
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221207140113-4f1c5845bf21
12-
github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
11+
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221208104547-250fb9731ae8
12+
github.com/ipfs/kubo v0.14.0-rc1
1313
github.com/libp2p/go-libp2p v0.23.4
1414
github.com/multiformats/go-multiaddr v0.8.0
1515
)

‎docs/examples/kubo-as-a-library/go.sum

+2-1
Original file line numberDiff line numberDiff line change
@@ -615,8 +615,9 @@ github.com/ipfs/go-unixfsnode v1.4.0/go.mod h1:qc7YFFZ8tABc58p62HnIYbUMwj9chhUuF
615615
github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0=
616616
github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs=
617617
github.com/ipfs/go-verifcid v0.0.2/go.mod h1:40cD9x1y4OWnFXbLNJYRe7MpNvWlMn3LZAG5Wb4xnPU=
618-
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221207140113-4f1c5845bf21 h1:iEbS/b3JgNaN8ayvLSQPe4FqHW35MiRU4ZT6G+seELA=
619618
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221207140113-4f1c5845bf21/go.mod h1:X/udt0qeqxXlgv69JQ8g38gWy4LrCyOuav6f7KDoJMo=
619+
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221208104547-250fb9731ae8 h1:JN89fRndO6CB+Rcqm3uFlWUXeGCuZ6/4atMmwT3A5dc=
620+
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221208104547-250fb9731ae8/go.mod h1:X/udt0qeqxXlgv69JQ8g38gWy4LrCyOuav6f7KDoJMo=
620621
github.com/ipld/edelweiss v0.2.0 h1:KfAZBP8eeJtrLxLhi7r3N0cBCo7JmwSRhOJp3WSpNjk=
621622
github.com/ipld/edelweiss v0.2.0/go.mod h1:FJAzJRCep4iI8FOFlRriN9n0b7OuX3T/S9++NpBDmA4=
622623
github.com/ipld/go-car v0.4.0 h1:U6W7F1aKF/OJMHovnOVdst2cpQE5GhmHibQkAixgNcQ=

‎go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ require (
6363
github.com/ipfs/go-unixfs v0.4.1
6464
github.com/ipfs/go-unixfsnode v1.4.0
6565
github.com/ipfs/go-verifcid v0.0.2
66-
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221207140113-4f1c5845bf21
66+
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221208104547-250fb9731ae8
6767
github.com/ipld/go-car v0.4.0
6868
github.com/ipld/go-car/v2 v2.4.0
6969
github.com/ipld/go-codec-dagpb v1.4.1

‎go.sum

+2-1
Original file line numberDiff line numberDiff line change
@@ -641,8 +641,9 @@ github.com/ipfs/go-unixfsnode v1.4.0/go.mod h1:qc7YFFZ8tABc58p62HnIYbUMwj9chhUuF
641641
github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0=
642642
github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs=
643643
github.com/ipfs/go-verifcid v0.0.2/go.mod h1:40cD9x1y4OWnFXbLNJYRe7MpNvWlMn3LZAG5Wb4xnPU=
644-
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221207140113-4f1c5845bf21 h1:iEbS/b3JgNaN8ayvLSQPe4FqHW35MiRU4ZT6G+seELA=
645644
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221207140113-4f1c5845bf21/go.mod h1:X/udt0qeqxXlgv69JQ8g38gWy4LrCyOuav6f7KDoJMo=
645+
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221208104547-250fb9731ae8 h1:JN89fRndO6CB+Rcqm3uFlWUXeGCuZ6/4atMmwT3A5dc=
646+
github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221208104547-250fb9731ae8/go.mod h1:X/udt0qeqxXlgv69JQ8g38gWy4LrCyOuav6f7KDoJMo=
646647
github.com/ipld/edelweiss v0.2.0 h1:KfAZBP8eeJtrLxLhi7r3N0cBCo7JmwSRhOJp3WSpNjk=
647648
github.com/ipld/edelweiss v0.2.0/go.mod h1:FJAzJRCep4iI8FOFlRriN9n0b7OuX3T/S9++NpBDmA4=
648649
github.com/ipld/go-car v0.4.0 h1:U6W7F1aKF/OJMHovnOVdst2cpQE5GhmHibQkAixgNcQ=

0 commit comments

Comments
 (0)