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

routing/http: add more type and WithDynamicProviderInfo #443

Closed
wants to merge 2 commits into from
Closed
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
49 changes: 44 additions & 5 deletions routing/http/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"fmt"
"mime"
"net/http"
"net/url"
"strings"
"time"

Expand Down Expand Up @@ -54,7 +55,7 @@
accepts string

peerID peer.ID
addrs []types.Multiaddr
addrs func() ([]types.Multiaddr, error)
identity crypto.PrivKey

// called immeidately after signing a provide req
Expand Down Expand Up @@ -104,10 +105,37 @@
}

func WithProviderInfo(peerID peer.ID, addrs []multiaddr.Multiaddr) Option {
taddrs := make([]types.Multiaddr, len(addrs))
for i, v := range addrs {
taddrs[i] = types.Multiaddr{Multiaddr: v}
}

return func(c *client) {
c.peerID = peerID
for _, a := range addrs {
c.addrs = append(c.addrs, types.Multiaddr{Multiaddr: a})
c.addrs = func() ([]types.Multiaddr, error) {
return taddrs, nil
}
}
}

// WithDynamicProviderInfo is like [WithProviderInfo] but the addresses will be queried on each publish operation.
// This is usefull for nodes with changing addresses, like P2P daemons behind NATs.
// Note: due to API limitations can't trivially batch update previous records with new addresses, so you are still relient
// on an consumers using a PeerRouter able to follow your new addresses, for example the IPFS DHT.
func WithDynamicProviderInfo(peerID peer.ID, addrs func() ([]multiaddr.Multiaddr, error)) Option {
return func(c *client) {
c.peerID = peerID
c.addrs = func() ([]types.Multiaddr, error) {
addrs, err := addrs()
if err != nil {
return nil, err
}

Check warning on line 132 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L131-L132

Added lines #L131 - L132 were not covered by tests

taddrs := make([]types.Multiaddr, len(addrs))
for i, v := range addrs {
taddrs[i] = types.Multiaddr{Multiaddr: v}
}
return taddrs, nil
}
}
}
Expand All @@ -120,6 +148,7 @@

// New creates a content routing API client.
// The Provider and identity parameters are option. If they are nil, the `Provide` method will not function.
// Consider using the more type-safe option [NewURL].
func New(baseURL string, opts ...Option) (*client, error) {
client := &client{
baseURL: baseURL,
Expand All @@ -140,6 +169,11 @@
return client, nil
}

// NewURL is a more type-safe version of [New], it takes in an [url.URL].
func NewURL(baseURL url.URL, opts ...Option) (*client, error) {
return New(baseURL.String(), opts...)

Check warning on line 174 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L173-L174

Added lines #L173 - L174 were not covered by tests
}

// measuringIter measures the length of the iter and then publishes metrics about the whole req once the iter is closed.
// Of course, if the caller forgets to close the iter, this won't publish anything.
type measuringIter[T any] struct {
Expand Down Expand Up @@ -251,6 +285,11 @@

now := c.clock.Now()

addrs, err := c.addrs()
if err != nil {
return 0, fmt.Errorf("failed to query our addresses: %w", err)
}

Check warning on line 291 in routing/http/client/client.go

View check run for this annotation

Codecov / codecov/patch

routing/http/client/client.go#L290-L291

Added lines #L290 - L291 were not covered by tests

req := types.WriteBitswapProviderRecord{
Protocol: "transport-bitswap",
Schema: types.SchemaBitswap,
Expand All @@ -259,10 +298,10 @@
AdvisoryTTL: &types.Duration{Duration: ttl},
Timestamp: &types.Time{Time: now},
ID: &c.peerID,
Addrs: c.addrs,
Addrs: addrs,
},
}
err := req.Sign(c.peerID, c.identity)
err = req.Sign(c.peerID, c.identity)
if err != nil {
return 0, err
}
Expand Down
72 changes: 69 additions & 3 deletions routing/http/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func drAddrsToAddrs(drmas []types.Multiaddr) (addrs []multiaddr.Multiaddr) {
return
}

func makeBSReadProviderResp() types.ReadBitswapProviderRecord {
func makeBSReadProviderResp(t *testing.T) types.ReadBitswapProviderRecord {
peerID, addrs, _ := makeProviderAndIdentity()
return types.ReadBitswapProviderRecord{
Protocol: "transport-bitswap",
Expand Down Expand Up @@ -194,7 +194,7 @@ func (e *osErrContains) errContains(t *testing.T, err error) {
}

func TestClient_FindProviders(t *testing.T) {
bsReadProvResp := makeBSReadProviderResp()
bsReadProvResp := makeBSReadProviderResp(t)
bitswapProvs := []iter.Result[types.ProviderResponse]{
{Val: &bsReadProvResp},
}
Expand Down Expand Up @@ -412,11 +412,18 @@ func TestClient_Provide(t *testing.T) {
}
}

var addrs []types.Multiaddr
if f := client.addrs; f != nil {
var err error
addrs, err = client.addrs()
require.NoError(t, err)
}

expectedProvReq := &server.BitswapWriteProvideRequest{
Keys: c.cids,
Timestamp: clock.Now().Truncate(time.Millisecond),
AdvisoryTTL: c.ttl,
Addrs: drAddrsToAddrs(client.addrs),
Addrs: drAddrsToAddrs(addrs),
ID: client.peerID,
}

Expand All @@ -442,3 +449,62 @@ func TestClient_Provide(t *testing.T) {
})
}
}

func TestWithDynamicClient(t *testing.T) {
t.Parallel()

const ttl = time.Hour

const testUserAgent = "testUserAgent"
peerID, addrs, identity := makeProviderAndIdentity()
router := &mockContentRouter{}
recordingHandler := &recordingHandler{
Handler: server.Handler(router),
f: []func(*http.Request){
func(r *http.Request) {
assert.Equal(t, testUserAgent, r.Header.Get("User-Agent"))
},
},
}
srv := httptest.NewServer(recordingHandler)
t.Cleanup(srv.Close)
serverAddr := "http://" + srv.Listener.Addr().String()
recordingHTTPClient := &recordingHTTPClient{httpClient: defaultHTTPClient}
var rAddrs []multiaddr.Multiaddr
client, err := New(serverAddr,
WithDynamicProviderInfo(peerID, func() ([]multiaddr.Multiaddr, error) { return rAddrs, nil }),
WithIdentity(identity),
WithUserAgent(testUserAgent),
WithHTTPClient(recordingHTTPClient),
)
require.NoError(t, err)

c := makeCID()
rAddrs = addrs[:1]

clock := clock.NewMock()
clock.Set(time.Now())
client.clock = clock

expectedProvReq := &server.BitswapWriteProvideRequest{
Keys: []cid.Cid{c},
Timestamp: clock.Now().Truncate(time.Millisecond),
AdvisoryTTL: ttl,
Addrs: rAddrs,
ID: peerID,
}
router.On("ProvideBitswap", mock.Anything, expectedProvReq).Return(ttl, nil)

ctx := context.Background()
_, err = client.ProvideBitswap(ctx, []cid.Cid{c}, ttl)
require.NoError(t, err)

c = makeCID()
rAddrs = addrs[1:]

expectedProvReq.Keys[0] = c
expectedProvReq.Addrs = rAddrs

_, err = client.ProvideBitswap(ctx, []cid.Cid{c}, ttl)
require.NoError(t, err)
}
6 changes: 3 additions & 3 deletions routing/http/contentrouter/contentrouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@
continue
}

var addrs []multiaddr.Multiaddr
for _, a := range result.Addrs {
addrs = append(addrs, a.Multiaddr)
addrs := make([]multiaddr.Multiaddr, len(result.Addrs))
for i, a := range result.Addrs {
addrs[i] = a.Multiaddr

Check warning on line 129 in routing/http/contentrouter/contentrouter.go

View check run for this annotation

Codecov / codecov/patch

routing/http/contentrouter/contentrouter.go#L129

Added line #L129 was not covered by tests
}

ch <- peer.AddrInfo{
Expand Down
5 changes: 3 additions & 2 deletions routing/http/contentrouter/contentrouter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ipfs/boxo/routing/http/types/iter"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
"github.com/multiformats/go-multihash"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
Expand Down Expand Up @@ -135,8 +136,8 @@ func TestFindProvidersAsync(t *testing.T) {
}

expected := []peer.AddrInfo{
{ID: p1},
{ID: p2},
{ID: p1, Addrs: []multiaddr.Multiaddr{}},
{ID: p2, Addrs: []multiaddr.Multiaddr{}},
}

require.Equal(t, expected, actualAIs)
Expand Down
8 changes: 4 additions & 4 deletions routing/http/internal/drjson/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ import (
"encoding/json"
)

func marshalJSON(val any) (*bytes.Buffer, error) {
func marshalJSON(val any) ([]byte, error) {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
err := enc.Encode(val)
return buf, err
return buf.Bytes(), err
}

// MarshalJSONBytes is needed to avoid changes
// on the original bytes due to HTML escapes.
func MarshalJSONBytes(val any) ([]byte, error) {
buf, err := marshalJSON(val)
bytes, err := marshalJSON(val)
if err != nil {
return nil, err
}

// remove last \n added by Encode
return buf.Bytes()[:buf.Len()-1], nil
return bytes[:len(bytes)-1], nil
}
Loading