Documentation ¶
Overview ¶
HTTP semantics with libp2p. Can use a libp2p stream transport or stock HTTP transports. This API is experimental and will likely change soon. Implements [libp2p spec #508](https://github.com/libp2p/specs/pull/508).
Index ¶
- Constants
- Variables
- func PreferHTTPTransport(o roundTripperOpts) roundTripperOpts
- func ServerMustAuthenticatePeerID(o roundTripperOpts) roundTripperOpts
- type Host
- func (h *Host) AddPeerMetadata(server peer.ID, meta PeerMeta)
- func (h *Host) Addrs() []ma.Multiaddr
- func (h *Host) Close() error
- func (h *Host) GetPeerMetadata(server peer.ID) (PeerMeta, bool)
- func (h *Host) NamespaceRoundTripper(roundtripper http.RoundTripper, p protocol.ID, server peer.ID) (*namespacedRoundTripper, error)
- func (h *Host) NamespacedClient(p protocol.ID, server peer.AddrInfo, opts ...RoundTripperOption) (http.Client, error)
- func (h *Host) NewConstrainedRoundTripper(server peer.AddrInfo, opts ...RoundTripperOption) (http.RoundTripper, error)
- func (h *Host) PeerID() peer.ID
- func (h *Host) RemovePeerMetadata(server peer.ID)
- func (h *Host) Serve() error
- func (h *Host) SetHTTPHandler(p protocol.ID, handler http.Handler)
- func (h *Host) SetHTTPHandlerAtPath(p protocol.ID, path string, handler http.Handler)
- func (h *Host) SetPeerMetadata(server peer.ID, meta PeerMeta)
- type PeerMeta
- type PeerMetadataGetter
- type ProtocolMeta
- type RoundTripperOption
- type WellKnownHandler
Examples ¶
Constants ¶
const LegacyWellKnownProtocols = "/.well-known/libp2p"
LegacyWellKnownProtocols refer to a the well-known resource used in an early draft of the libp2p+http spec. Some users have deployed this, and need backwards compatibility. Hopefully we can phase this out in the future. Context: https://github.com/libp2p/go-libp2p/pull/2797
const ProtocolIDForMultistreamSelect = "/http/1.1"
const WellKnownProtocols = "/.well-known/libp2p/protocols"
Variables ¶
var ErrNoListeners = errors.New("nothing to listen on")
var WellKnownRequestTimeout = 30 * time.Second
Functions ¶
func PreferHTTPTransport ¶
func PreferHTTPTransport(o roundTripperOpts) roundTripperOpts
PreferHTTPTransport tells the roundtripper constructor to prefer using an HTTP transport (as opposed to a libp2p stream transport). Useful, for example, if you want to attempt to leverage HTTP caching.
func ServerMustAuthenticatePeerID ¶
func ServerMustAuthenticatePeerID(o roundTripperOpts) roundTripperOpts
ServerMustAuthenticatePeerID tells the roundtripper constructor that we MUST authenticate the Server's PeerID. Note: this currently means we can not use a native HTTP transport (HTTP peer id authentication is not yet implemented: https://github.com/libp2p/specs/pull/564).
Types ¶
type Host ¶
type Host struct { // StreamHost is a stream based libp2p host used to do HTTP over libp2p streams. May be nil StreamHost host.Host // ListenAddrs are the requested addresses to listen on. Multiaddrs must be // valid HTTP(s) multiaddr. Only multiaddrs for an HTTP transport are // supported (must end with /http or /https). ListenAddrs []ma.Multiaddr // TLSConfig is the TLS config for the server to use TLSConfig *tls.Config // InsecureAllowHTTP indicates if the server is allowed to serve unencrypted // HTTP requests over TCP. InsecureAllowHTTP bool // ServeMux is the http.ServeMux used by the server to serve requests. If // nil, a new serve mux will be created. Users may manually add handlers to // this mux instead of using `SetHTTPHandler`, but if they do, they should // also update the WellKnownHandler's protocol mapping. ServeMux *http.ServeMux // DefaultClientRoundTripper is the default http.RoundTripper for clients to // use when making requests over an HTTP transport. This must be an // `*http.Transport` type so that the transport can be cloned and the // `TLSClientConfig` field can be configured. If unset, it will create a new // `http.Transport` on first use. DefaultClientRoundTripper *http.Transport // WellKnownHandler is the http handler for the well-known // resource. It is responsible for sharing this node's protocol metadata // with other nodes. Users only care about this if they set their own // ServeMux with pre-existing routes. By default, new protocols are added // here when a user calls `SetHTTPHandler` or `SetHTTPHandlerAtPath`. WellKnownHandler WellKnownHandler // EnableCompatibilityWithLegacyWellKnownEndpoint allows compatibility with // an older version of the spec that defined the well-known resource as: // .well-known/libp2p. // For servers, this means hosting the well-known resource at both the // legacy and current paths. // For clients it means making two parallel requests and picking the first one that succeeds. // // Long term this should be deprecated once enough users have upgraded to a // newer go-libp2p version and we can remove all this code. EnableCompatibilityWithLegacyWellKnownEndpoint bool // contains filtered or unexported fields }
Host is a libp2p host for request/responses with HTTP semantics. This is in contrast to a stream-oriented host like the core host.Host interface. Its zero-value (&Host{}) is usable. Do not copy by value. See examples for usage.
Warning, this is experimental. The API will likely change.
Example (ListenOnHTTPTransportAndStreams) ¶
serverStreamHost, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic-v1")) if err != nil { log.Fatal(err) } defer serverStreamHost.Close() server := libp2phttp.Host{ InsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/0/http")}, StreamHost: serverStreamHost, } go server.Serve() defer server.Close() for _, a := range server.Addrs() { _, transport := ma.SplitLast(a) fmt.Printf("Server listening on transport: %s\n", transport) }
Output: Server listening on transport: /quic-v1 Server listening on transport: /http
Example (OverLibp2pStreams) ¶
serverStreamHost, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic-v1")) if err != nil { log.Fatal(err) } server := libp2phttp.Host{ StreamHost: serverStreamHost, } // A server with a simple echo protocol server.SetHTTPHandler("/echo/1.0.0", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/octet-stream") io.Copy(w, r.Body) })) go server.Serve() defer server.Close() clientStreamHost, err := libp2p.New(libp2p.NoListenAddrs) if err != nil { log.Fatal(err) } client := libp2phttp.Host{StreamHost: clientStreamHost} // Make an HTTP request using the Go standard library, but over libp2p // streams. If the server were listening on an HTTP transport, this could // also make the request over the HTTP transport. httpClient, err := client.NamespacedClient("/echo/1.0.0", peer.AddrInfo{ID: server.PeerID(), Addrs: server.Addrs()}) // Only need to Post to "/" because this client is namespaced to the "/echo/1.0.0" protocol. resp, err := httpClient.Post("/", "application/octet-stream", strings.NewReader("Hello HTTP")) if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(body))
Output: Hello HTTP
Example (WithAStockGoHTTPClient) ¶
package main import ( "fmt" "io" "log" "net/http" "strings" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" ma "github.com/multiformats/go-multiaddr" ) func main() { server := libp2phttp.Host{ InsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/0/http")}, } // A server with a simple echo protocol server.SetHTTPHandler("/echo/1.0.0", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/octet-stream") io.Copy(w, r.Body) })) go server.Serve() defer server.Close() var serverHTTPPort string var err error for _, a := range server.Addrs() { serverHTTPPort, err = a.ValueForProtocol(ma.P_TCP) if err == nil { break } } if err != nil { log.Fatal(err) } // Make an HTTP request using the Go standard library. resp, err := http.Post("http://127.0.0.1:"+serverHTTPPort+"/echo/1.0.0/", "application/octet-stream", strings.NewReader("Hello HTTP")) if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(body)) }
Output: Hello HTTP
func (*Host) AddPeerMetadata ¶
AddPeerMetadata merges the given peer's protocol metadata to the http host. Useful if you have out-of-band knowledge of a peer's protocol mapping.
func (*Host) GetPeerMetadata ¶
GetPeerMetadata gets a peer's cached protocol metadata from the http host.
func (*Host) NamespaceRoundTripper ¶
func (h *Host) NamespaceRoundTripper(roundtripper http.RoundTripper, p protocol.ID, server peer.ID) (*namespacedRoundTripper, error)
NamespaceRoundTripper returns an http.RoundTripper that are scoped to the given protocol on the given server.
Example ¶
package main import ( "fmt" "io" "log" "net/http" "github.com/libp2p/go-libp2p/core/peer" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" ma "github.com/multiformats/go-multiaddr" ) func main() { var client libp2phttp.Host // Create the server server := libp2phttp.Host{ InsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/0/http")}, } server.SetHTTPHandler("/hello/1", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "text/plain") w.Write([]byte("Hello World")) })) go server.Serve() defer server.Close() // Create an http.Roundtripper for the server rt, err := client.NewConstrainedRoundTripper(peer.AddrInfo{ID: server.PeerID(), Addrs: server.Addrs()}) if err != nil { log.Fatal(err) } // Namespace this roundtripper to a specific protocol rt, err = client.NamespaceRoundTripper(rt, "/hello/1", server.PeerID()) if err != nil { log.Fatal(err) } resp, err := (&http.Client{Transport: rt}).Get("/") if err != nil { log.Fatal(err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(respBody)) }
Output: Hello World
func (*Host) NamespacedClient ¶
func (h *Host) NamespacedClient(p protocol.ID, server peer.AddrInfo, opts ...RoundTripperOption) (http.Client, error)
NamespacedClient returns an http.Client that is scoped to the given protocol on the given server. It creates a new RoundTripper for each call. If you are creating many namespaced clients, consider creating a round tripper directly and namespacing the roundripper yourself, then creating clients from the namespace round tripper.
Example ¶
package main import ( "fmt" "io" "log" "net/http" "github.com/libp2p/go-libp2p/core/peer" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" ma "github.com/multiformats/go-multiaddr" ) func main() { var client libp2phttp.Host // Create the server server := libp2phttp.Host{ InsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/0/http")}, } server.SetHTTPHandlerAtPath("/hello/1", "/other-place/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "text/plain") w.Write([]byte("Hello World")) })) go server.Serve() defer server.Close() // Create an http.Client that is namespaced to this protocol. httpClient, err := client.NamespacedClient("/hello/1", peer.AddrInfo{ID: server.PeerID(), Addrs: server.Addrs()}) if err != nil { log.Fatal(err) } resp, err := httpClient.Get("/") if err != nil { log.Fatal(err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(respBody)) }
Output: Hello World
func (*Host) NewConstrainedRoundTripper ¶
func (h *Host) NewConstrainedRoundTripper(server peer.AddrInfo, opts ...RoundTripperOption) (http.RoundTripper, error)
NewConstrainedRoundTripper returns an http.RoundTripper that can fulfill and HTTP request to the given server. It may use an HTTP transport or a stream based transport. It is valid to pass an empty server.ID. If there are multiple addresses for the server, it will pick the best transport (stream vs standard HTTP) using the following rules:
- If PreferHTTPTransport is set, use the HTTP transport.
- If ServerMustAuthenticatePeerID is set, use the stream transport, as the HTTP transport does not do peer id auth yet.
- If we already have a connection on a stream transport, use that.
- Otherwise, if we have both, use the HTTP transport.
Example ¶
package main import ( "fmt" "io" "log" "net/http" "github.com/libp2p/go-libp2p/core/peer" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" ma "github.com/multiformats/go-multiaddr" ) func main() { var client libp2phttp.Host // Create the server server := libp2phttp.Host{ InsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/0/http")}, } server.SetHTTPHandler("/hello/1", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "text/plain") w.Write([]byte("Hello World")) })) go server.Serve() defer server.Close() // Create an http.Roundtripper for the server rt, err := client.NewConstrainedRoundTripper(peer.AddrInfo{ID: server.PeerID(), Addrs: server.Addrs()}) if err != nil { log.Fatal(err) } resp, err := (&http.Client{Transport: rt}).Get("/hello/1") if err != nil { log.Fatal(err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(respBody)) }
Output: Hello World
func (*Host) PeerID ¶
ID returns the peer ID of the underlying stream host, or the zero value if there is no stream host.
func (*Host) RemovePeerMetadata ¶
RemovePeerMetadata removes a peer's protocol metadata from the http host
func (*Host) Serve ¶
Serve starts the HTTP transport listeners. Always returns a non-nil error. If there are no listeners, returns ErrNoListeners.
Example ¶
package main import ( "fmt" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" ma "github.com/multiformats/go-multiaddr" ) func main() { server := libp2phttp.Host{ InsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/50221/http")}, } go server.Serve() defer server.Close() fmt.Println(server.Addrs()) }
Output: [/ip4/127.0.0.1/tcp/50221/http]
func (*Host) SetHTTPHandler ¶
SetHTTPHandler sets the HTTP handler for a given protocol. Automatically manages the well-known resource mapping. http.StripPrefix is called on the handler, so the handler will be unaware of its prefix path.
Example ¶
package main import ( "fmt" "io" "log" "net/http" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" ma "github.com/multiformats/go-multiaddr" ) func main() { server := libp2phttp.Host{ InsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/0/http")}, } server.SetHTTPHandler("/hello/1", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "text/plain") w.Write([]byte("Hello World")) })) go server.Serve() defer server.Close() port, err := server.Addrs()[0].ValueForProtocol(ma.P_TCP) if err != nil { log.Fatal(err) } resp, err := http.Get("http://127.0.0.1:" + port + "/hello/1/") if err != nil { log.Fatal(err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(respBody)) }
Output: Hello World
func (*Host) SetHTTPHandlerAtPath ¶
SetHTTPHandlerAtPath sets the HTTP handler for a given protocol using the given path. Automatically manages the well-known resource mapping. http.StripPrefix is called on the handler, so the handler will be unaware of its prefix path.
Example ¶
package main import ( "fmt" "io" "log" "net/http" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" ma "github.com/multiformats/go-multiaddr" ) func main() { server := libp2phttp.Host{ InsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP ListenAddrs: []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/0/http")}, } server.SetHTTPHandlerAtPath("/hello/1", "/other-place/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "text/plain") w.Write([]byte("Hello World")) })) go server.Serve() defer server.Close() port, err := server.Addrs()[0].ValueForProtocol(ma.P_TCP) if err != nil { log.Fatal(err) } resp, err := http.Get("http://127.0.0.1:" + port + "/other-place/") if err != nil { log.Fatal(err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(respBody)) }
Output: Hello World
type PeerMeta ¶
type PeerMeta map[protocol.ID]ProtocolMeta
type PeerMetadataGetter ¶
PeerMetadataGetter lets RoundTrippers implement a specific way of caching a peer's protocol mapping.
type ProtocolMeta ¶
type ProtocolMeta struct { // Path defines the HTTP Path prefix used for this protocol Path string `json:"path"` }
ProtocolMeta is metadata about a protocol.
type RoundTripperOption ¶
type RoundTripperOption func(o roundTripperOpts) roundTripperOpts
type WellKnownHandler ¶
type WellKnownHandler struct {
// contains filtered or unexported fields
}
WellKnownHandler is an http.Handler that serves the well-known resource
Example ¶
package main import ( "fmt" "io" "log" "net" "net/http" libp2phttp "github.com/libp2p/go-libp2p/p2p/http" ) func main() { var h libp2phttp.WellKnownHandler h.AddProtocolMeta("/hello/1", libp2phttp.ProtocolMeta{ Path: "/hello-path/", }) listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { log.Fatal(err) } defer listener.Close() // Serve the well-known resource. Note, this is handled automatically if you use the libp2phttp.Host. go http.Serve(listener, &h) // Get the well-known resource resp, err := http.Get("http://" + listener.Addr().String() + libp2phttp.WellKnownProtocols) if err != nil { log.Fatal(err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(respBody)) }
Output: {"/hello/1":{"path":"/hello-path/"}}
func (*WellKnownHandler) AddProtocolMeta ¶
func (h *WellKnownHandler) AddProtocolMeta(p protocol.ID, protocolMeta ProtocolMeta)
func (*WellKnownHandler) RemoveProtocolMeta ¶
func (h *WellKnownHandler) RemoveProtocolMeta(p protocol.ID)
func (*WellKnownHandler) ServeHTTP ¶
func (h *WellKnownHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)