-
Notifications
You must be signed in to change notification settings - Fork 20
/
p2phttp.go
165 lines (146 loc) · 4.5 KB
/
p2phttp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Package p2phttp allows to serve HTTP endpoints and make HTTP requests through
// LibP2P (https://github.com/libp2p/libp2p) using Go's standard "http" and
// "net" stacks.
//
// Instead of the regular "host:port" addressing, `p2phttp` uses a Peer ID
// and lets LibP2P take care of the routing, thus taking advantage
// of features like multi-routes, NAT transversal and stream multiplexing
// over a single connection.
//
// When already running a LibP2P facility, this package allows to expose
// existing HTTP-based services (like REST APIs) through LibP2P and to
// use those services with minimal changes to the code-base.
//
// For example, a simple http.Server on LibP2P works as:
//
// listener, _ := gostream.Listen(host1, p2phttp.DefaultP2PProtocol)
// defer listener.Close()
// go func() {
// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
// w.Write([]byte("Hi!"))
// })
// server := &http.Server{}
// server.Serve(listener)
// }
// ...
//
// As shown above, a Server only needs a
// "github.com/libp2p/go-libp2p-gostream" listener. This listener will
// use a libP2P host to watch for stream tagged with our Protocol.
//
// On the other side, a client just needs to be initialized with a custom
// LibP2P host-based transport to perform requests to such server:
//
// tr := &http.Transport{}
// tr.RegisterProtocol("libp2p", p2phttp.NewTransport(clientHost))
// client := &http.Client{Transport: tr}
// res, err := client.Get("libp2p://Qmaoi4isbcTbFfohQyn28EiYM5CDWQx9QRCjDh3CTeiY7P/hello")
// ...
//
// In the example above, the client registers a "libp2p" protocol for which the
// custom transport is used. It can still perform regular "http" requests. The
// protocol name used is arbitraty and non standard.
//
// Note that LibP2P hosts cannot dial to themselves, so there is no possibility
// of using the same host as server and as client.
package p2phttp
import (
"bufio"
"io"
"net"
"net/http"
gostream "github.com/libp2p/go-libp2p-gostream"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
)
// DefaultP2PProtocol is used to tag and identify streams
// handled by go-libp2p-http
var DefaultP2PProtocol protocol.ID = "/libp2p-http"
// options holds configuration options for the transport.
type options struct {
Protocol protocol.ID
}
// Option allows to set the libp2p transport options.
type Option func(o *options)
// ProtocolOption sets the Protocol Tag associated to the libp2p roundtripper.
func ProtocolOption(p protocol.ID) Option {
return func(o *options) {
o.Protocol = p
}
}
// RoundTripper implemenets http.RoundTrip and can be used as
// custom transport with Go http.Client.
type RoundTripper struct {
h host.Host
opts options
}
// NewTransport returns a new RoundTripper which uses the provided
// libP2P host to perform an http request and obtain the response.
//
// The typical use case for NewTransport is to register the "libp2p"
// protocol with a Transport, as in:
//
// t := &http.Transport{}
// t.RegisterProtocol("libp2p", p2phttp.NewTransport(host, ProtocolOption(DefaultP2PProtocol)))
// c := &http.Client{Transport: t}
// res, err := c.Get("libp2p://Qmaoi4isbcTbFfohQyn28EiYM5CDWQx9QRCjDh3CTeiY7P/index.html")
// ...
func NewTransport(h host.Host, opts ...Option) *RoundTripper {
defOpts := options{
Protocol: DefaultP2PProtocol,
}
for _, o := range opts {
o(&defOpts)
}
return &RoundTripper{h, defOpts}
}
// we wrap the response body and close the stream
// only when it's closed.
type respBody struct {
io.ReadCloser
conn net.Conn
}
// Closes the response's body and the connection.
func (rb *respBody) Close() error {
rb.conn.Close()
return rb.ReadCloser.Close()
}
// RoundTrip executes a single HTTP transaction, returning
// a Response for the provided Request.
func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
addr := r.Host
if addr == "" {
addr = r.URL.Host
}
pid, err := peer.Decode(addr)
if err != nil {
return nil, err
}
conn, err := gostream.Dial(r.Context(), rt.h, peer.ID(pid), rt.opts.Protocol)
if err != nil {
if r.Body != nil {
r.Body.Close()
}
return nil, err
}
// Write the request while reading the response
go func() {
err := r.Write(conn)
if err != nil {
conn.Close()
}
if r.Body != nil {
r.Body.Close()
}
}()
resp, err := http.ReadResponse(bufio.NewReader(conn), r)
if err != nil {
return resp, err
}
resp.Body = &respBody{
ReadCloser: resp.Body,
conn: conn,
}
return resp, nil
}