[http_proxy_over_p2p]
This implements an http-proxy over p2p-streams. License: MIT Signed-off-by: Chris Boddy <chris@boddy.im>
Chris committed
Aug 22, 2018 at 18:56 UTC
90021c16d6bde306564ff28fa13f01ec1ba3bae4
5 files changed
+121
-7
cmd/ipfs/daemon.go
+1
@@ -461,6 +461,7 @@ func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error
461
corehttp.MutexFractionOption("/debug/pprof-mutex/"),
462
corehttp.MetricsScrapingOption("/debug/metrics/prometheus"),
463
corehttp.LogOption(),
464
+ corehttp.ProxyOption(),
465
}
466
467
if len(cfg.Gateway.RootRedirect) > 0 {
p2p/local.go
+1
-1
@@ -55,7 +55,7 @@ func (l *localListener) dial(ctx context.Context) (net.Stream, error) {
55
cctx, cancel := context.WithTimeout(ctx, time.Second*30) //TODO: configurable?
56
defer cancel()
57
58
- return l.p2p.peerHost.NewStream(cctx, l.peer, l.proto)
58
+ return l.p2p.PeerHost.NewStream(cctx, l.peer, l.proto)
59
}
60
61
func (l *localListener) acceptConns() {
p2p/p2p.go
+6
-6
@@ -16,23 +16,23 @@ type P2P struct {
16
Streams *StreamRegistry
17
18
identity peer.ID
19
- peerHost p2phost.Host
19
+ PeerHost p2phost.Host
20
peerstore pstore.Peerstore
21
}
22
23
// NewP2P creates new P2P struct
24
-func NewP2P(identity peer.ID, peerHost p2phost.Host, peerstore pstore.Peerstore) *P2P {
24
+func NewP2P(identity peer.ID, PeerHost p2phost.Host, peerstore pstore.Peerstore) *P2P {
25
return &P2P{
26
identity: identity,
27
- peerHost: peerHost,
27
+ PeerHost: PeerHost,
28
peerstore: peerstore,
29
30
ListenersLocal: newListenersLocal(),
31
- ListenersP2P: newListenersP2P(peerHost),
31
+ ListenersP2P: newListenersP2P(PeerHost),
32
33
Streams: &StreamRegistry{
34
Streams: map[uint64]*Stream{},
35
- ConnManager: peerHost.ConnManager(),
35
+ ConnManager: PeerHost.ConnManager(),
36
conns: map[peer.ID]int{},
37
},
38
}
@@ -41,7 +41,7 @@ func NewP2P(identity peer.ID, peerHost p2phost.Host, peerstore pstore.Peerstore)
41
// CheckProtoExists checks whether a proto handler is registered to
42
// mux handler
43
func (p2p *P2P) CheckProtoExists(proto string) bool {
44
- protos := p2p.peerHost.Mux().Protocols()
44
+ protos := p2p.PeerHost.Mux().Protocols()
45
46
for _, p := range protos {
47
if p != proto {
p2p/proxy.go
new
+92
@@ -0,0 +1,92 @@
1
+package p2p
2
+
3
+import (
4
+ "bufio"
5
+ "fmt"
6
+ "net"
7
+ "net/http"
8
+ "strings"
9
+
10
+ core "github.com/ipfs/go-ipfs/core"
11
+ protocol "gx/ipfs/QmZNkThpqfVXs9GNbexPrfBbXSLNYeKrE7jwFM2oqHbyqN/go-libp2p-protocol"
12
+ peer "gx/ipfs/QmbNepETomvmXfz1X5pHNFD2QuPqnqi47dTd94QJWSorQ3/go-libp2p-peer"
13
+)
14
+
15
+func ProxyOption() ServeOption {
16
+ return func(ipfsNode *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
17
+ mux.HandleFunc("/proxy/", func(w http.ResponseWriter, request *http.Request) {
18
+ // parse request
19
+ parsedRequest, err := parseRequest(request)
20
+ if err != nil {
21
+ handleError(w, "Failed to parse request", err, 400)
22
+ return
23
+ }
24
+
25
+ // open connect to peer
26
+ stream, err := ipfsNode.P2P.PeerHost.NewStream(ipfsNode.Context(), parsedRequest.target, protocol.ID("/x/"+parsedRequest.name))
27
+ if err != nil {
28
+ msg := fmt.Sprintf("Failed to open stream '%v' to target peer '%v'", parsedRequest.name, parsedRequest.target)
29
+ handleError(w, msg, err, 500)
30
+ return
31
+ }
32
+
33
+ // send request to peer
34
+ proxyReq, err := http.NewRequest(request.Method, parsedRequest.httpPath, request.Body)
35
+
36
+ if err != nil {
37
+ handleError(w, "Failed to format proxy request", err, 500)
38
+ return
39
+ }
40
+
41
+ proxyReq.Write(stream)
42
+
43
+ s := bufio.NewReader(stream)
44
+ proxyResponse, err := http.ReadResponse(s, proxyReq)
45
+ defer func() { proxyResponse.Body.Close() }()
46
+ if err != nil {
47
+ msg := fmt.Sprintf("Failed to send request to stream '%v' to peer '%v'", parsedRequest.name, parsedRequest.target)
48
+ handleError(w, msg, err, 500)
49
+ return
50
+ }
51
+ // send client response
52
+ proxyResponse.Write(w)
53
+ })
54
+ return mux, nil
55
+ }
56
+}
57
+
58
+type proxyRequest struct {
59
+ target peer.ID
60
+ name string
61
+ httpPath string // path to send to the proxy-host
62
+}
63
+
64
+// from the url path parse the peer-ID, name and http path
65
+// /http/$peer_id/$name/$http_path
66
+func parseRequest(request *http.Request) (*proxyRequest, error) {
67
+ path := request.URL.Path
68
+
69
+ split := strings.SplitN(path, "/", 6)
70
+ if split[2] != "http" {
71
+ return nil, fmt.Errorf("Invalid proxy request protocol '%s'", path)
72
+ }
73
+
74
+ if len(split) < 6 {
75
+ return nil, fmt.Errorf("Invalid request path '%s'", path)
76
+ }
77
+
78
+ peerID, err := peer.IDB58Decode(split[3])
79
+
80
+ if err != nil {
81
+ return nil, err
82
+ }
83
+
84
+ return &proxyRequest{peerID, split[4], split[5]}, nil
85
+}
86
+
87
+// log error and send response to client
88
+func handleError(w http.ResponseWriter, msg string, err error, code int) {
89
+ w.WriteHeader(code)
90
+ fmt.Fprintf(w, "%s: %s\n", msg, err)
91
+ log.Warningf("server error: %s: %s", err)
92
+}
p2p/proxy_test.go
new
+21
@@ -0,0 +1,21 @@
1
+package p2p
2
+
3
+import (
4
+ "github.com/ipfs/go-ipfs/thirdparty/assert"
5
+ "net/http"
6
+ "strings"
7
+ "testing"
8
+)
9
+
10
+func TestParseRequest(t *testing.T) {
11
+ url := "http://localhost:5001/proxy/http/QmT8JtU54XSmC38xSb1XHFSMm775VuTeajg7LWWWTAwzxT/test-name/path/to/index.txt"
12
+ req, _ := http.NewRequest("GET", url, strings.NewReader(""))
13
+
14
+ parsed, err := parseRequest(req)
15
+ if err != nil {
16
+ t.Error(err)
17
+ }
18
+ assert.True(parsed.httpPath == "path/to/index.txt", t, "proxy request path")
19
+ assert.True(parsed.name == "test-name", t, "proxy request name")
20
+ assert.True(parsed.target.Pretty() == "QmT8JtU54XSmC38xSb1XHFSMm775VuTeajg7LWWWTAwzxT", t, "proxy request peer-id")
21
+}