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: Implement HTTP spec #2438

Merged
merged 72 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
deef00e
Add SNI and HTTP_libp2p_token to Noise extensions
MarcoPolo Jul 20, 2023
d92727b
Initial libp2phttp work
MarcoPolo Jul 20, 2023
e814a07
Remove custom response writer
MarcoPolo Jul 20, 2023
9d9426b
Initial self review
MarcoPolo Jul 20, 2023
de3cdd4
Add client for PingHTTP
MarcoPolo Jul 21, 2023
61957ef
Support using a different sni from host
MarcoPolo Aug 2, 2023
587839f
Add WIP auth support
MarcoPolo Aug 2, 2023
4baeba3
Revert "Add WIP auth support"
MarcoPolo Aug 2, 2023
89e78fe
Remove libp2p-noise auth (removed from spec)
MarcoPolo Aug 2, 2023
30d348a
wip notes
MarcoPolo Aug 2, 2023
4642050
Fix ordering of header writes
MarcoPolo Aug 3, 2023
2555502
Change api to have the host do more
MarcoPolo Aug 4, 2023
f183e2e
Add options
MarcoPolo Aug 4, 2023
aeeaeac
Use stream host from option instead of parameter
MarcoPolo Aug 4, 2023
b7ab538
Nits
MarcoPolo Aug 4, 2023
44f330a
Add AddPeerMetadata
MarcoPolo Aug 7, 2023
6f18bdb
Add CustomRootHandler option
MarcoPolo Aug 8, 2023
63cfaf1
Remove old todos
MarcoPolo Aug 8, 2023
8753458
Undo Noise changes
MarcoPolo Aug 8, 2023
5070407
Add comments
MarcoPolo Aug 8, 2023
b7b3a1a
Implement CloseIdleConnections to protect from surprising behavior
MarcoPolo Aug 9, 2023
67cd05e
Add todo
MarcoPolo Aug 9, 2023
b60680d
Add ServerMustAuthenticatePeerID option
MarcoPolo Aug 11, 2023
9dad142
WIP work on recent roundtripper logic
MarcoPolo Aug 11, 2023
24d61f3
Remove recentHTTPAddrs. We don't need it
MarcoPolo Aug 11, 2023
c53875f
Move http ping to separate package
MarcoPolo Aug 16, 2023
6ca42d9
Hide internal constants
MarcoPolo Aug 16, 2023
f2a2914
HTTPHost has a valid zero-value. Remove constructor and options
MarcoPolo Aug 16, 2023
8156bbb
Add https test
MarcoPolo Aug 16, 2023
5e2e19e
Rename to following naming convention
MarcoPolo Aug 16, 2023
5b5db88
Add flag for insecure http
MarcoPolo Aug 16, 2023
5857757
Return after error
MarcoPolo Aug 16, 2023
eee5836
Rename Rm to Remove
MarcoPolo Aug 16, 2023
a6cadd9
Rename
MarcoPolo Aug 16, 2023
1ce1915
Refactor to always call closeAllListeners
MarcoPolo Aug 16, 2023
37c79a6
Rename
MarcoPolo Aug 16, 2023
e05ea11
Rename
MarcoPolo Aug 16, 2023
17c08ed
Automatically strip prefix when using SetHTTPHandler*
MarcoPolo Aug 16, 2023
b1fc4c1
Hide streamHostListen
MarcoPolo Aug 16, 2023
d56ee6e
Cleanup public types and add docs
MarcoPolo Aug 16, 2023
6cb071d
Fix interface rename
MarcoPolo Aug 16, 2023
00e8f17
Remove debug
MarcoPolo Aug 16, 2023
7eaadb4
PR comments
MarcoPolo Aug 17, 2023
b5417c3
Add examples
MarcoPolo Aug 18, 2023
c3873dd
Fix example name
MarcoPolo Aug 18, 2023
b5dcff1
Cleanup unused example
MarcoPolo Aug 18, 2023
6906148
Add more examples
MarcoPolo Aug 18, 2023
eea1710
Add well known handler example
MarcoPolo Aug 19, 2023
e756bad
Handle empty path
MarcoPolo Aug 19, 2023
b8c1380
Fix typo
MarcoPolo Aug 19, 2023
647653d
Make RoundTripperOption public so users can allocate a slice of options
MarcoPolo Aug 21, 2023
1f12a58
Rename HTTPHost to Host
MarcoPolo Aug 21, 2023
c31efcd
Make the host.WellKnownHandler public
MarcoPolo Aug 21, 2023
ac47208
Make Add merge PeerMetadata. Introduce SetPeerMetadata
MarcoPolo Aug 23, 2023
403b282
Rename AddProtocolMapping to AddProtocolMeta. Expand comment
MarcoPolo Aug 23, 2023
8b9e11d
Expand comment on DefaultClientRoundTripper
MarcoPolo Aug 23, 2023
eb9aa17
Remove todo
MarcoPolo Aug 23, 2023
791e0f3
Fix comment typo
MarcoPolo Aug 23, 2023
e53fa35
Fix comment typo
MarcoPolo Aug 23, 2023
419660b
Create helper init fn
MarcoPolo Aug 23, 2023
80fbb7d
Rename NewRoundTripper to NewConstrainedRoundTripper
MarcoPolo Aug 24, 2023
8c18fe4
Use pointer for Host.ServeMux
MarcoPolo Aug 25, 2023
c65d655
Don't ignore err
MarcoPolo Aug 28, 2023
34c5818
json decode from reader
MarcoPolo Aug 28, 2023
a4de205
Nits
MarcoPolo Aug 28, 2023
a6176a7
Move setupListeners to method
MarcoPolo Aug 28, 2023
fcd5c05
Add comment for streamReadCloser
MarcoPolo Aug 28, 2023
39adff6
Add more comments
MarcoPolo Aug 28, 2023
5f75f4e
Add todo
MarcoPolo Aug 28, 2023
9bca68a
Defer connect until the round trip
MarcoPolo Aug 28, 2023
1ecf648
Rebase gostream
MarcoPolo Aug 28, 2023
04ec0c0
Update p2p/http/libp2phttp.go
MarcoPolo Aug 28, 2023
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
353 changes: 353 additions & 0 deletions p2p/http/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
package libp2phttp_test

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

"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/peer"
libp2phttp "github.com/libp2p/go-libp2p/p2p/http"
ma "github.com/multiformats/go-multiaddr"
)

func ExampleHost_withAStockGoHTTPClient() {
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 ExampleHost_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]
}

func ExampleHost_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
}

func ExampleHost_Serve() {
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 ExampleHost_SetHTTPHandler() {
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 ExampleHost_SetHTTPHandlerAtPath() {
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 ExampleHost_NamespacedClient() {
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 ExampleHost_NamespaceRoundTripper() {
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 ExampleHost_NewConstrainedRoundTripper() {
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 ExampleWellKnownHandler() {
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/"}}

}
Loading