Skip to content

Conversation

@lidel
Copy link
Member

@lidel lidel commented Oct 13, 2025

This PR implements IPIP-518 which extends /routing/v1 with schema-agnostic support for URIs alongside multiaddrs.

Key changes:

  • Add new Address type that accepts both multiaddrs (starting with /) and URIs
  • Update PeerRecord and BitswapRecord to use Addresses type instead of []Multiaddr
  • Implement defensive programming: unsupported addresses are skipped, not errors
  • Sensible UX:
    • protocol filtering for http/https/tls matching real-world user intent (we expect https:// to be used more and more across the stack)
    • in places where only Multiaddr is returned, we turn https:// urls into /https multiaddrs
  • Backward compatibility with multiaddr-only implementations which currently depend on /https multiaddrs

The implementation is not limited to https://, is schema-agnostic, accepting any valid URI scheme for future extensibility. URLs are preserved as-is without lossy conversion to multiaddrs, avoiding historical conversion issues documented in the IPIP.

IPIP:

TODO

…ing API

Implements schema-agnostic support for HTTP(S) URLs alongside multiaddrs
in the Delegated Routing V1 HTTP API as specified in IPIP-518.

Key changes:
- Add new Address type that accepts both multiaddrs (starting with /) and URIs
- Update PeerRecord and BitswapRecord to use Addresses type instead of []Multiaddr
- Implement defensive programming: unsupported addresses are skipped, not errors
- Add special protocol filtering for http/https/tls matching
- Maintain full backward compatibility with multiaddr-only implementations

The implementation is schema-agnostic, accepting any valid URI scheme for
future extensibility. URLs are preserved as-is without lossy conversion
to multiaddrs, avoiding conversion issues documented in multiaddr#63.

Ref: ipfs/specs#518
@lidel lidel changed the title feat(routing/http): implement IPIP-518 HTTP(S) URLs in Delegated Rout… feat(routing/http): IPIP-518: HTTP(S) URLs in Routing V1 API Oct 13, 2025
Implements backward compatibility layer for HTTP(S) URL to multiaddr
conversion during transition period. Uses /https (not /tls/http) to
match IPNI convention and /dns for dual-stack support.

- Add ToMultiaddr() method and String() for Addresses type
- Replace TODOs in contentrouter with actual conversion
- Add comprehensive tests

Temporary compatibility layer until ecosystem adopts IPIP-518 URLs.
@codecov
Copy link

codecov bot commented Oct 13, 2025

Codecov Report

❌ Patch coverage is 79.83871% with 50 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.85%. Comparing base (0f1ebac) to head (c9ac579).
⚠️ Report is 18 commits behind head on main.

Files with missing lines Patch % Lines
routing/http/types/address.go 81.59% 31 Missing and 6 partials ⚠️
routing/http/contentrouter/contentrouter.go 33.33% 8 Missing ⚠️
routing/http/filters/filters.go 81.48% 4 Missing and 1 partial ⚠️

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1051      +/-   ##
==========================================
+ Coverage   60.75%   60.85%   +0.09%     
==========================================
  Files         268      269       +1     
  Lines       33593    33805     +212     
==========================================
+ Hits        20411    20572     +161     
- Misses      11510    11554      +44     
- Partials     1672     1679       +7     
Files with missing lines Coverage Δ
routing/http/client/client.go 74.82% <100.00%> (+0.05%) ⬆️
routing/http/server/server.go 74.22% <100.00%> (+0.05%) ⬆️
routing/http/types/record_bitswap.go 40.69% <ø> (ø)
routing/http/types/record_peer.go 83.63% <100.00%> (ø)
routing/http/filters/filters.go 79.59% <81.48%> (-3.05%) ⬇️
routing/http/contentrouter/contentrouter.go 50.00% <33.33%> (-0.57%) ⬇️
routing/http/types/address.go 81.59% <81.59%> (ø)

... and 9 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lidel lidel marked this pull request as ready for review October 13, 2025 21:17
@lidel lidel requested a review from a team as a code owner October 13, 2025 21:17
Comment on lines +26 to +51
func NewAddress(s string) (*Address, error) {
addr := &Address{raw: s}

// IPIP-518 parsing logic
if strings.HasPrefix(s, "/") {
// Parse as multiaddr
ma, err := multiaddr.NewMultiaddr(s)
if err != nil {
return nil, fmt.Errorf("invalid multiaddr: %w", err)
}
addr.multiaddr = ma
} else {
// Parse as URI - accept any valid URI scheme
u, err := url.Parse(s)
if err != nil {
return nil, fmt.Errorf("invalid URI: %w", err)
}
// Must be absolute URL
if !u.IsAbs() {
return nil, fmt.Errorf("URI must be absolute")
}
addr.url = u
}

return addr, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every time I see this called, the returned *Address is immediately dereferenced. This indicates that it would be better to return an Address struct directly, and not a pointer to said struct. Also, all the types inside are effectively pointers, so return the struct literal does not incur any overhead to worry about.

Suggested change
func NewAddress(s string) (*Address, error) {
addr := &Address{raw: s}
// IPIP-518 parsing logic
if strings.HasPrefix(s, "/") {
// Parse as multiaddr
ma, err := multiaddr.NewMultiaddr(s)
if err != nil {
return nil, fmt.Errorf("invalid multiaddr: %w", err)
}
addr.multiaddr = ma
} else {
// Parse as URI - accept any valid URI scheme
u, err := url.Parse(s)
if err != nil {
return nil, fmt.Errorf("invalid URI: %w", err)
}
// Must be absolute URL
if !u.IsAbs() {
return nil, fmt.Errorf("URI must be absolute")
}
addr.url = u
}
return addr, nil
}
func NewAddress(s string) (Address, error) {
addr := Address{raw: s}
// IPIP-518 parsing logic
if strings.HasPrefix(s, "/") {
// Parse as multiaddr
ma, err := multiaddr.NewMultiaddr(s)
if err != nil {
return Address{}, fmt.Errorf("invalid multiaddr: %w", err)
}
addr.multiaddr = ma
} else {
// Parse as URI - accept any valid URI scheme
u, err := url.Parse(s)
if err != nil {
return Address{}, fmt.Errorf("invalid uri: %w", err)
}
// Must be absolute URL
if !u.IsAbs() {
return Address{}, fmt.Errorf("uri must be absolute")
}
addr.url = u
}
return addr, nil
}

nit: Do not capitalize "URL" and "URI" in error message as that is not consistent with golang stdlib:
https://cs.opensource.google/go/go/+/refs/tags/go1.25.3:src/net/url/url.go;l=521

func (a *Address) Protocols() []string {
if a.url != nil {
return []string{a.url.Scheme}
} else if a.multiaddr != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if a.multiaddr != nil {
}
if a.multiaddr != nil {

Comment on lines +187 to +189
// "tls" matches any multiaddr with /tls
for _, p := range protocols {
if p == "tls" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also match "https"?

Comment on lines +175 to +183
if p == "https" {
return true
}
if p == "tls" {
hasTLS = true
}
if p == "http" {
hasHTTP = true
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if p == "https" {
return true
}
if p == "tls" {
hasTLS = true
}
if p == "http" {
hasHTTP = true
}
if p == "https" {
switch p {
case "https":
return true
case "tls":
hasTLS = true
case "http":
hasHTTP = true
}

Comment on lines +254 to +262
var maStr string
if scheme == "https" {
// For HTTPS, use /https as this is what existing HTTP providers
// announce on IPNI, so we follow same convention for backward-compatibility
maStr = fmt.Sprintf("/%s/%s/tcp/%s/https", addrProto, host, port)
} else {
// For HTTP, use /http
maStr = fmt.Sprintf("/%s/%s/tcp/%s/http", addrProto, host, port)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since scheme can only be "http" or "https" just use scheme, instead of the if-else.

Suggested change
var maStr string
if scheme == "https" {
// For HTTPS, use /https as this is what existing HTTP providers
// announce on IPNI, so we follow same convention for backward-compatibility
maStr = fmt.Sprintf("/%s/%s/tcp/%s/https", addrProto, host, port)
} else {
// For HTTP, use /http
maStr = fmt.Sprintf("/%s/%s/tcp/%s/http", addrProto, host, port)
}
// For HTTPS, use /https as this is what existing HTTP providers
// announce on IPNI, so we follow same convention for backward-compatibility
maStr := fmt.Sprintf("/%s/%s/tcp/%s/%s", addrProto, host, port, scheme)

// Returns nil if the address cannot be converted (e.g., non-HTTP schemes or invalid addresses).
// This is a temporary compatibility layer for the transition period while existing software
// expects multiaddrs with /http protocol to signal HTTP retrieval support.
func (a *Address) ToMultiaddr() multiaddr.Multiaddr {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative to this that avoids hardcoded values for addrProto can be found here:
https://github.com/ipni/go-libipni/blob/main/maurl/convert.go#L110-L156

Both approaches work. The go-libipni version has had years of testing and operation if you want to cut and paste that one, but that does not mean it is necessarily better. I think perhaps using megular expressions would be better still.

@gammazero gammazero added the status/in-progress In progress label Nov 4, 2025
@gammazero
Copy link
Contributor

Triage: Consider updating IPIP to suggest different peer schema.
Blocked waiting for more progress in buoy to determine what is needed here.

Note: Should not assume trustless gateway when HTTP URI with no metadata

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants