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: libp2phttp /http-path #2850

Merged
merged 10 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ require (
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b
github.com/mr-tron/base58 v1.2.0
github.com/multiformats/go-base32 v0.1.0
github.com/multiformats/go-multiaddr v0.12.4
github.com/multiformats/go-multiaddr v0.13.0
github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/multiformats/go-multiaddr-fmt v0.1.0
github.com/multiformats/go-multibase v0.2.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,8 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4=
github.com/multiformats/go-multiaddr v0.12.4 h1:rrKqpY9h+n80EwhhC/kkcunCZZ7URIF8yN1WEUt2Hvc=
github.com/multiformats/go-multiaddr v0.12.4/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII=
github.com/multiformats/go-multiaddr v0.13.0 h1:BCBzs61E3AGHcYYTv8dqRH43ZfyrqM8RXVPT8t13tLQ=
github.com/multiformats/go-multiaddr v0.13.0/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII=
github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A=
github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk=
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
Expand Down
129 changes: 108 additions & 21 deletions p2p/http/libp2phttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,10 @@ var ErrNoListeners = errors.New("nothing to listen on")

func (h *Host) setupListeners(listenerErrCh chan error) error {
for _, addr := range h.ListenAddrs {
parsedAddr := parseMultiaddr(addr)
parsedAddr, err := parseMultiaddr(addr)
if err != nil {
return err
}
// resolve the host
ipaddr, err := net.ResolveIPAddr("ip", parsedAddr.host)
if err != nil {
Expand Down Expand Up @@ -477,9 +480,59 @@ func (rt *streamRoundTripper) RoundTrip(r *http.Request) (*http.Response, error)
}
resp.Body = &streamReadCloser{resp.Body, s}

locUrl, err := resp.Location()
if err == nil {
// Location url in response. Is this a multiaddr uri? and is it relative?
// If it's relative we want to convert it to an absolute multiaddr uri
// so that the next request knows how to reach the endpoint.
if locUrl.Scheme == "multiaddr" && resp.Request.URL.Scheme == "multiaddr" {
// Check if it's a relative URI and turn it into an absolute one
u, err := relativeMultiaddrURIToAbs(resp.Request.URL, locUrl)
if err == nil {
// It was a relative URI and we were able to convert it to an absolute one
// Update the location header to be an absolute multiaddr uri
resp.Header.Set("Location", u.String())
}
}
}

return resp, nil
}

// relativeMultiaddrURIToAbs takes a relative multiaddr URI and turns it into an
// absolute one. Useful, for example, when a server gives us a relative URI for a redirect.
// It allows the following request (the one after redirected) to reach the correct server.
func relativeMultiaddrURIToAbs(original *url.URL, relative *url.URL) (*url.URL, error) {
// Is this a relative uri? We know if it is because non-relative URI's of the form:
// "multiaddr:/ip4/1.2.3.4/tcp/9899" when parsed by Go's url package will have url.OmitHost == true
// But if it is relative (just a path to an http resource e.g. /here-instead)
// a redirect will inherit the multiaddr scheme, but set url.OmitHost == false. It will also stringify as something like
// multiaddr://here-instead.
if relative.OmitHost {
// Not relative (at least we can't tell). Nothing we can do here
return nil, errors.New("not relative")
Copy link
Member

Choose a reason for hiding this comment

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

You can reuse this error and avoid allocation for all the calls. This looks like it'll be used often.

}
originalStr := original.RawPath
if originalStr == "" {
originalStr = original.Path
}
originalMa, err := ma.NewMultiaddr(originalStr)
if err != nil {
return nil, errors.New("original uri is not a multiaddr")
}

relativePathComponent, err := ma.NewComponent("http-path", relative.Path)
if err != nil {
return nil, errors.New("relative path is not a valid http-path")
}

withoutPath, _ := ma.SplitFunc(originalMa, func(c ma.Component) bool {
return c.Protocol().Code == ma.P_HTTP_PATH
})
withNewPath := withoutPath.Encapsulate(relativePathComponent)
return url.Parse("multiaddr:" + withNewPath.String())
}

// roundTripperForSpecificServer is an http.RoundTripper targets a specific server. Still reuses the underlying RoundTripper for the requests.
// The underlying RoundTripper MUST be an HTTP Transport.
type roundTripperForSpecificServer struct {
Expand Down Expand Up @@ -657,12 +710,23 @@ func (h *Host) RoundTrip(r *http.Request) (*http.Response, error) {
return nil, err
}
addr, isHTTP := normalizeHTTPMultiaddr(addr)
sukunrt marked this conversation as resolved.
Show resolved Hide resolved
parsed, err := parseMultiaddr(addr)
Copy link
Member

@sukunrt sukunrt Jun 27, 2024

Choose a reason for hiding this comment

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

We need this parsing only if it's a http multiaddr, right? Otherwise we can just use the addr?

I would prefer calling this only when it's an /http multiaddr because parseMultiaddr looks specific to parsing /http multiaddrs. Having said that, I haven't been able to find any multiaddr that would make parseMultiaddr do something funny.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We need this parsing only if it's a http multiaddr, right?

Both. We use the http-path, host, and port in both code paths.

Would you prefer we name this something else?

Copy link
Member

Choose a reason for hiding this comment

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

You only need parsed.peer in that code path so I was wondering if we should just get the peerID from the addr in that code path.

This is fine too though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We need http path as well

if err != nil {
return nil, err
}

if isHTTP {
parsed := parseMultiaddr(addr)
scheme := "http"
if parsed.useHTTPS {
scheme = "https"
}
u := url.URL{
Scheme: scheme,
Host: parsed.host + ":" + parsed.port,
Path: parsed.httpPath,
}
r.URL = &u

h.initDefaultRT()
rt := h.DefaultClientRoundTripper
if parsed.sni != parsed.host {
Expand All @@ -676,28 +740,30 @@ func (h *Host) RoundTrip(r *http.Request) (*http.Response, error) {
rt.TLSClientConfig.ServerName = parsed.sni
}

// TODO add http-path support
url := url.URL{
Scheme: scheme,
Host: parsed.host + ":" + parsed.port,
}

r.URL = &url
return rt.RoundTrip(r)
}

if h.StreamHost == nil {
return nil, fmt.Errorf("can not do HTTP over streams. Missing StreamHost")
}

addr, pid := peer.SplitAddr(addr)
if pid == "" {
if parsed.peer == "" {
return nil, fmt.Errorf("no peer ID in multiaddr")
}
h.StreamHost.Peerstore().AddAddrs(pid, []ma.Multiaddr{addr}, peerstore.TempAddrTTL)
withoutHTTPPath, _ := ma.SplitFunc(addr, func(c ma.Component) bool {
return c.Protocol().Code == ma.P_HTTP_PATH
})
h.StreamHost.Peerstore().AddAddrs(parsed.peer, []ma.Multiaddr{withoutHTTPPath}, peerstore.TempAddrTTL)

// Set the Opaque field to the http-path so that the HTTP request only makes
// a reference to that path and not the whole multiaddr uri
r.URL.Opaque = parsed.httpPath
if r.Host == "" {
// Fill in the host if it's not already set
r.Host = parsed.host + ":" + parsed.port
}
srt := streamRoundTripper{
server: pid,
server: parsed.peer,
skipAddAddrs: true,
httpHost: h,
h: h.StreamHost,
Expand Down Expand Up @@ -750,7 +816,10 @@ func (h *Host) NewConstrainedRoundTripper(server peer.AddrInfo, opts ...RoundTri

// Currently the HTTP transport can not authenticate peer IDs.
if !options.serverMustAuthenticatePeerID && len(httpAddrs) > 0 && (options.preferHTTPTransport || (firstAddrIsHTTP && !existingStreamConn)) {
parsed := parseMultiaddr(httpAddrs[0])
parsed, err := parseMultiaddr(httpAddrs[0])
if err != nil {
return nil, err
}
scheme := "http"
if parsed.useHTTPS {
scheme = "https"
Expand Down Expand Up @@ -791,15 +860,18 @@ func (h *Host) NewConstrainedRoundTripper(server peer.AddrInfo, opts ...RoundTri
return &streamRoundTripper{h: h.StreamHost, server: server.ID, serverAddrs: nonHTTPAddrs, httpHost: h}, nil
}

type httpMultiaddr struct {
type explodedMultiaddr struct {
useHTTPS bool
host string
port string
sni string
httpPath string
peer peer.ID
}

func parseMultiaddr(addr ma.Multiaddr) httpMultiaddr {
out := httpMultiaddr{}
func parseMultiaddr(addr ma.Multiaddr) (explodedMultiaddr, error) {
out := explodedMultiaddr{}
var err error
ma.ForEach(addr, func(c ma.Component) bool {
switch c.Protocol().Code {
case ma.P_IP4, ma.P_IP6, ma.P_DNS, ma.P_DNS4, ma.P_DNS6:
Expand All @@ -810,15 +882,27 @@ func parseMultiaddr(addr ma.Multiaddr) httpMultiaddr {
out.useHTTPS = true
case ma.P_SNI:
out.sni = c.Value()

case ma.P_HTTP_PATH:
out.httpPath, err = url.QueryUnescape(c.Value())
if err == nil && out.httpPath[0] != '/' {
out.httpPath = "/" + out.httpPath
}
case ma.P_P2P:
out.peer, err = peer.Decode(c.Value())
}
return out.host == "" || out.port == "" || !out.useHTTPS || out.sni == ""

// stop if there is an error, otherwise iterate over all components in case this is a circuit address
return err == nil
})

if out.useHTTPS && out.sni == "" {
out.sni = out.host
}
return out

if out.httpPath == "" {
out.httpPath = "/"
}
return out, err
}

var httpComponent, _ = ma.NewComponent("http", "")
Expand All @@ -839,6 +923,9 @@ func normalizeHTTPMultiaddr(addr ma.Multiaddr) (ma.Multiaddr, bool) {
}
return false
})
if beforeHTTPS == nil || !isHTTPMultiaddr {
return addr, false
}

if afterIncludingHTTPS == nil {
// No HTTPS component, just return the original
Expand Down Expand Up @@ -918,7 +1005,7 @@ func (h *Host) getAndStorePeerMetadata(ctx context.Context, roundtripper http.Ro
}

func requestPeerMeta(ctx context.Context, roundtripper http.RoundTripper, wellKnownResource string) (PeerMeta, error) {
req, err := http.NewRequest("GET", wellKnownResource, nil)
req, err := http.NewRequestWithContext(ctx, "GET", wellKnownResource, nil)
if err != nil {
return nil, err
}
Expand Down
Loading
Loading