Skip to content

Commit 68c7df3

Browse files
committed
gateway: initial hostname-based routing
Replace IPNSHostname based routing with general-purpose hostname-based routing that also supports handling requests to "known gateways". TODO: I'll break KnownGateways out into a config option in a later commit. License: MIT Signed-off-by: Steven Allen <[email protected]>
1 parent 73010e6 commit 68c7df3

File tree

5 files changed

+103
-41
lines changed

5 files changed

+103
-41
lines changed

cmd/ipfs/daemon.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
637637
var opts = []corehttp.ServeOption{
638638
corehttp.ProxyOption(), // Proxies all CONNECT requests to self. Always put this first.
639639
corehttp.MetricsCollectionOption("gateway"),
640-
corehttp.IPNSHostnameOption(),
640+
corehttp.HostnameOption(),
641641
corehttp.GatewayOption(writable, "/ipfs", "/ipns"),
642642
corehttp.VersionOption(),
643643
corehttp.CheckVersionOption(),

core/corehttp/gateway_handler.go

+23-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,28 @@ const (
3131
ipnsPathPrefix = "/ipns/"
3232
)
3333

34+
type GatewaySpec struct {
35+
// PathPrefixes list the set of path prefixes that should be handled by
36+
// the gateway logic.
37+
PathPrefixes []string
38+
39+
// SupportsSubdomains indicates whether or not this gateway supports
40+
// requests of the form http://CID.ipfs.GATEWAY/...
41+
SupportsSubdomains bool
42+
}
43+
44+
var DefaultGatewaySpec = GatewaySpec{
45+
PathPrefixes: []string{ipfsPathPrefix, ipnsPathPrefix, "/api/"},
46+
SupportsSubdomains: true,
47+
}
48+
49+
// TODO(steb): Configurable
50+
var KnownGateways = map[string]GatewaySpec{
51+
"ipfs.io": DefaultGatewaySpec,
52+
"gateway.ipfs.io": DefaultGatewaySpec,
53+
"localhost:8080": DefaultGatewaySpec,
54+
}
55+
3456
// gatewayHandler is a HTTP handler that serves IPFS objects (accessible by default at /ipfs/<path>)
3557
// (it serves requests like GET /ipfs/QmVRzPKPzNtSrEzBFm2UZfxmPAgnaLke4DMcerbsGGSaFe/link)
3658
type gatewayHandler struct {
@@ -142,7 +164,7 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
142164
}
143165
}
144166

145-
// IPNSHostnameOption might have constructed an IPNS path using the Host header.
167+
// HostnameOption might have constructed an IPNS/IPFS path using the Host header.
146168
// In this case, we need the original path for constructing redirects
147169
// and links that match the requested URL.
148170
// For example, http://example.net would become /ipns/example.net, and

core/corehttp/gateway_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func newTestServerAndNode(t *testing.T, ns mockNamesys) (*httptest.Server, iface
138138

139139
dh.Handler, err = makeHandler(n,
140140
ts.Listener,
141-
IPNSHostnameOption(),
141+
HostnameOption(),
142142
GatewayOption(false, "/ipfs", "/ipns"),
143143
VersionOption(),
144144
)

core/corehttp/hostname.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package corehttp
2+
3+
import (
4+
"context"
5+
"net"
6+
"net/http"
7+
"strings"
8+
9+
core "github.com/ipfs/go-ipfs/core"
10+
namesys "github.com/ipfs/go-ipfs/namesys"
11+
12+
nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys"
13+
isd "github.com/jbenet/go-is-domain"
14+
)
15+
16+
// HostnameOption rewrites an incoming request based on the Host header.
17+
func HostnameOption() ServeOption {
18+
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
19+
childMux := http.NewServeMux()
20+
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
21+
ctx, cancel := context.WithCancel(n.Context())
22+
defer cancel()
23+
24+
// Is this one of our "known gateways"?
25+
if gw, ok := KnownGateways[r.Host]; ok {
26+
if hasPrefix(r.URL.Path, gw.PathPrefixes...) {
27+
childMux.ServeHTTP(w, r)
28+
return
29+
}
30+
} else if host, pathPrefix, ok := parseSubdomains(r.Host); ok {
31+
if gw, ok := KnownGateways[host]; ok && gw.SupportsSubdomains {
32+
// Always handle this with the gateway.
33+
// We don't care if it's one of the
34+
// valid path-prefixes.
35+
36+
r.URL.Path = pathPrefix + r.URL.Path
37+
childMux.ServeHTTP(w, r)
38+
return
39+
}
40+
}
41+
42+
host := strings.SplitN(r.Host, ":", 2)[0]
43+
if len(host) == 0 || !isd.IsDomain(host) {
44+
childMux.ServeHTTP(w, r)
45+
return
46+
}
47+
48+
name := "/ipns/" + host
49+
_, err := n.Namesys.Resolve(ctx, name, nsopts.Depth(1))
50+
if err == nil || err == namesys.ErrResolveRecursion {
51+
r.URL.Path = name + r.URL.Path
52+
}
53+
childMux.ServeHTTP(w, r)
54+
})
55+
return childMux, nil
56+
}
57+
}
58+
59+
func parseSubdomains(host string) (newHost, pathPrefix string, ok bool) {
60+
parts := strings.SplitN(host, ".", 3)
61+
if len(parts) < 3 {
62+
return "", "", false
63+
}
64+
switch parts[1] {
65+
case "ipfs", "ipns", "p2p":
66+
return parts[2], "/" + parts[1] + "/" + parts[0], true
67+
}
68+
return "", "", false
69+
}
70+
71+
func hasPrefix(s string, prefixes ...string) bool {
72+
for _, prefix := range prefixes {
73+
if strings.HasPrefix(s, prefix) {
74+
return true
75+
}
76+
}
77+
return false
78+
}

core/corehttp/ipns_hostname.go

-38
This file was deleted.

0 commit comments

Comments
 (0)