| 1 | package corehttp |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "net" |
| 6 | "net/http" |
| 7 | "net/http/httputil" |
| 8 | "net/url" |
| 9 | "strings" |
| 10 | |
| 11 | core "github.com/ipfs/kubo/core" |
| 12 | peer "github.com/libp2p/go-libp2p/core/peer" |
| 13 | |
| 14 | p2phttp "github.com/libp2p/go-libp2p-http" |
| 15 | protocol "github.com/libp2p/go-libp2p/core/protocol" |
| 16 | ) |
| 17 | |
| 18 | // P2PProxyOption is an endpoint for proxying a HTTP request to another ipfs peer |
| 19 | func P2PProxyOption() ServeOption { |
| 20 | return func(ipfsNode *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { |
| 21 | mux.HandleFunc("/p2p/", func(w http.ResponseWriter, request *http.Request) { |
| 22 | // parse request |
| 23 | parsedRequest, err := parseRequest(request) |
| 24 | if err != nil { |
| 25 | handleError(w, "failed to parse request", err, 400) |
| 26 | return |
| 27 | } |
| 28 | |
| 29 | request.Host = "" // Let URL's Host take precedence. |
| 30 | request.URL.Path = parsedRequest.httpPath |
| 31 | target, err := url.Parse(fmt.Sprintf("libp2p://%s", parsedRequest.target)) |
| 32 | if err != nil { |
| 33 | handleError(w, "failed to parse url", err, 400) |
| 34 | return |
| 35 | } |
| 36 | |
| 37 | rt := p2phttp.NewTransport(ipfsNode.PeerHost, p2phttp.ProtocolOption(parsedRequest.name)) |
| 38 | proxy := &httputil.ReverseProxy{ |
| 39 | Transport: rt, |
| 40 | Rewrite: func(r *httputil.ProxyRequest) { |
| 41 | r.SetURL(target) |
| 42 | r.SetXForwarded() |
| 43 | }, |
| 44 | } |
| 45 | proxy.ServeHTTP(w, request) |
| 46 | }) |
| 47 | return mux, nil |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | type proxyRequest struct { |
| 52 | target string |
| 53 | name protocol.ID |
| 54 | httpPath string // path to send to the proxy-host |
| 55 | } |
| 56 | |
| 57 | // from the url path parse the peer-ID, name and http path |
| 58 | // /p2p/$peer_id/http/$http_path |
| 59 | // or |
| 60 | // /p2p/$peer_id/x/$protocol/http/$http_path |
| 61 | func parseRequest(request *http.Request) (*proxyRequest, error) { |
| 62 | path := request.URL.Path |
| 63 | |
| 64 | split := strings.SplitN(path, "/", 5) |
| 65 | if len(split) < 5 { |
| 66 | return nil, fmt.Errorf("invalid request path '%s'", path) |
| 67 | } |
| 68 | |
| 69 | if _, err := peer.Decode(split[2]); err != nil { |
| 70 | return nil, fmt.Errorf("invalid request path '%s'", path) |
| 71 | } |
| 72 | |
| 73 | if split[3] == "http" { |
| 74 | return &proxyRequest{split[2], protocol.ID("/http"), split[4]}, nil |
| 75 | } |
| 76 | |
| 77 | split = strings.SplitN(path, "/", 7) |
| 78 | if len(split) < 7 || split[3] != "x" || split[5] != "http" { |
| 79 | return nil, fmt.Errorf("invalid request path '%s'", path) |
| 80 | } |
| 81 | |
| 82 | return &proxyRequest{split[2], protocol.ID("/x/" + split[4] + "/http"), split[6]}, nil |
| 83 | } |
| 84 | |
| 85 | func handleError(w http.ResponseWriter, msg string, err error, code int) { |
| 86 | http.Error(w, fmt.Sprintf("%s: %s", msg, err), code) |
| 87 | } |