libp2phttp

package
v0.31.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2023 License: MIT Imports: 21 Imported by: 0

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

Examples

Constants

View Source
const ProtocolIDForMultistreamSelect = "/http/1.1"

Variables

View Source
var ErrNoListeners = errors.New("nothing to listen on")

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/libp2p`
	// 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
	// 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/50124/quic-v1"))
if err != nil {
	log.Fatal(err)
}
server := libp2phttp.Host{
	InsecureAllowHTTP: true, // For our example, we'll allow insecure HTTP
	ListenAddrs:       []ma.Multiaddr{ma.StringCast("/ip4/127.0.0.1/tcp/50124/http")},
	StreamHost:        serverStreamHost,
}
go server.Serve()
defer server.Close()

fmt.Println("Server listening on:", server.Addrs())
Output:

Server listening on: [/ip4/127.0.0.1/udp/50124/quic-v1 /ip4/127.0.0.1/tcp/50124/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"

	ma "github.com/multiformats/go-multiaddr"

	libp2phttp "github.com/zbliujia/go-libp2p/p2p/http"
)

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

func (h *Host) AddPeerMetadata(server peer.ID, meta PeerMeta)

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) Addrs

func (h *Host) Addrs() []ma.Multiaddr

func (*Host) Close

func (h *Host) Close() error

func (*Host) GetPeerMetadata

func (h *Host) GetPeerMetadata(server peer.ID) (PeerMeta, bool)

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"

	ma "github.com/multiformats/go-multiaddr"
	"github.com/zbliujia/go-libp2p/core/peer"

	libp2phttp "github.com/zbliujia/go-libp2p/p2p/http"
)

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/50223/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"

	ma "github.com/multiformats/go-multiaddr"
	"github.com/zbliujia/go-libp2p/core/peer"

	libp2phttp "github.com/zbliujia/go-libp2p/p2p/http"
)

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/50221/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"

	ma "github.com/multiformats/go-multiaddr"
	"github.com/zbliujia/go-libp2p/core/peer"

	libp2phttp "github.com/zbliujia/go-libp2p/p2p/http"
)

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/50225/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

func (h *Host) PeerID() peer.ID

ID returns the peer ID of the underlying stream host, or the zero value if there is no stream host.

func (*Host) RemovePeerMetadata

func (h *Host) RemovePeerMetadata(server peer.ID)

RemovePeerMetadata removes a peer's protocol metadata from the http host

func (*Host) Serve

func (h *Host) Serve() error

Serve starts the HTTP transport listeners. Always returns a non-nil error. If there are no listeners, returns ErrNoListeners.

Example
package main

import (
	"fmt"

	ma "github.com/multiformats/go-multiaddr"

	libp2phttp "github.com/zbliujia/go-libp2p/p2p/http"
)

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

func (h *Host) SetHTTPHandler(p protocol.ID, handler http.Handler)

SetHTTPHandler sets the HTTP handler for a given protocol. Automatically manages the .well-known/libp2p 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"

	ma "github.com/multiformats/go-multiaddr"

	libp2phttp "github.com/zbliujia/go-libp2p/p2p/http"
)

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/50222/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

func (h *Host) SetHTTPHandlerAtPath(p protocol.ID, path string, handler http.Handler)

SetHTTPHandlerAtPath sets the HTTP handler for a given protocol using the given path. Automatically manages the .well-known/libp2p 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"

	ma "github.com/multiformats/go-multiaddr"

	libp2phttp "github.com/zbliujia/go-libp2p/p2p/http"
)

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/50224/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

func (*Host) SetPeerMetadata

func (h *Host) SetPeerMetadata(server peer.ID, meta PeerMeta)

SetPeerMetadata adds a peer's protocol metadata to the http host. Useful if you have out-of-band knowledge of a peer's protocol mapping.

type PeerMeta

type PeerMeta map[protocol.ID]ProtocolMeta

type PeerMetadataGetter

type PeerMetadataGetter interface {
	GetPeerMetadata() (PeerMeta, error)
}

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/libp2p resource

Example
package main

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/http"

	libp2phttp "github.com/zbliujia/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 `.well-known/libp2p`. Note, this is handled automatically if you use the libp2phttp.Host.
	go http.Serve(listener, &h)

	// Get the `.well-known/libp2p` resource
	resp, err := http.Get("http://" + listener.Addr().String() + "/.well-known/libp2p")
	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)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL