p2p_proxy.go 2.26 KB
Newer Older
1
package corehttp
Chris's avatar
Chris committed
2 3 4

import (
	"fmt"
5
	"net"
Chris's avatar
Chris committed
6
	"net/http"
7
	"net/http/httputil"
Hector Sanjuan's avatar
Hector Sanjuan committed
8
	"net/url"
Chris's avatar
Chris committed
9 10
	"strings"

tavit ohanian's avatar
tavit ohanian committed
11 12
	core "gitlab.dms3.io/dms3/go-dms3/core"
	peer "gitlab.dms3.io/p2p/go-p2p-core/peer"
13

tavit ohanian's avatar
tavit ohanian committed
14 15
	protocol "gitlab.dms3.io/p2p/go-p2p-core/protocol"
	p2phttp "gitlab.dms3.io/p2p/go-p2p-http"
Chris's avatar
Chris committed
16 17
)

tavit ohanian's avatar
tavit ohanian committed
18
// P2PProxyOption is an endpoint for proxying a HTTP request to another dms3 peer
19
func P2PProxyOption() ServeOption {
tavit ohanian's avatar
tavit ohanian committed
20
	return func(dms3Node *core.Dms3Node, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
21
		mux.HandleFunc("/p2p/", func(w http.ResponseWriter, request *http.Request) {
Chris's avatar
Chris committed
22 23 24
			// parse request
			parsedRequest, err := parseRequest(request)
			if err != nil {
25
				handleError(w, "failed to parse request", err, 400)
Chris's avatar
Chris committed
26 27 28
				return
			}

29
			request.Host = "" // Let URL's Host take precedence.
30
			request.URL.Path = parsedRequest.httpPath
tavit ohanian's avatar
tavit ohanian committed
31
			target, err := url.Parse(fmt.Sprintf("p2p://%s", parsedRequest.target))
Chris's avatar
Chris committed
32
			if err != nil {
33
				handleError(w, "failed to parse url", err, 400)
Chris's avatar
Chris committed
34 35
				return
			}
Hector Sanjuan's avatar
Hector Sanjuan committed
36

tavit ohanian's avatar
tavit ohanian committed
37
			rt := p2phttp.NewTransport(dms3Node.PeerHost, p2phttp.ProtocolOption(parsedRequest.name))
Hector Sanjuan's avatar
Hector Sanjuan committed
38 39 40
			proxy := httputil.NewSingleHostReverseProxy(target)
			proxy.Transport = rt
			proxy.ServeHTTP(w, request)
Chris's avatar
Chris committed
41 42 43 44 45 46
		})
		return mux, nil
	}
}

type proxyRequest struct {
Hector Sanjuan's avatar
Hector Sanjuan committed
47 48
	target   string
	name     protocol.ID
Chris's avatar
Chris committed
49 50 51 52
	httpPath string // path to send to the proxy-host
}

// from the url path parse the peer-ID, name and http path
53 54 55
// /p2p/$peer_id/http/$http_path
// or
// /p2p/$peer_id/x/$protocol/http/$http_path
Chris's avatar
Chris committed
56 57 58
func parseRequest(request *http.Request) (*proxyRequest, error) {
	path := request.URL.Path

59 60
	split := strings.SplitN(path, "/", 5)
	if len(split) < 5 {
Chris's avatar
Chris committed
61 62 63
		return nil, fmt.Errorf("Invalid request path '%s'", path)
	}

64 65 66 67
	if _, err := peer.Decode(split[2]); err != nil {
		return nil, fmt.Errorf("Invalid request path '%s'", path)
	}

68 69 70 71 72
	if split[3] == "http" {
		return &proxyRequest{split[2], protocol.ID("/http"), split[4]}, nil
	}

	split = strings.SplitN(path, "/", 7)
73
	if len(split) < 7 || split[3] != "x" || split[5] != "http" {
74 75 76 77
		return nil, fmt.Errorf("Invalid request path '%s'", path)
	}

	return &proxyRequest{split[2], protocol.ID("/x/" + split[4] + "/http"), split[6]}, nil
Chris's avatar
Chris committed
78 79 80
}

func handleError(w http.ResponseWriter, msg string, err error, code int) {
81
	http.Error(w, fmt.Sprintf("%s: %s", msg, err), code)
Chris's avatar
Chris committed
82
}