Skip to content

Commit a31c796

Browse files
authored
Merge pull request ipfs/kubo#6096 from ipfs/feat/gateway-subdomains
feat: gateway subdomains + http proxy mode This commit was moved from ipfs/kubo@0114869
2 parents 7ffb0b2 + 6ac0716 commit a31c796

8 files changed

+582
-107
lines changed

gateway/core/corehttp/corehttp.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,17 @@ func makeHandler(n *core.IpfsNode, l net.Listener, options ...ServeOption) (http
4343
return nil, err
4444
}
4545
}
46-
return topMux, nil
46+
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
47+
// ServeMux does not support requests with CONNECT method,
48+
// so we need to handle them separately
49+
// https://golang.org/src/net/http/request.go#L111
50+
if r.Method == http.MethodConnect {
51+
w.WriteHeader(http.StatusOK)
52+
return
53+
}
54+
topMux.ServeHTTP(w, r)
55+
})
56+
return handler, nil
4757
}
4858

4959
// ListenAndServe runs an HTTP server listening at |listeningMultiAddr| with
@@ -70,6 +80,8 @@ func ListenAndServe(n *core.IpfsNode, listeningMultiAddr string, options ...Serv
7080
return Serve(n, manet.NetListener(list), options...)
7181
}
7282

83+
// Serve accepts incoming HTTP connections on the listener and pass them
84+
// to ServeOption handlers.
7385
func Serve(node *core.IpfsNode, lis net.Listener, options ...ServeOption) error {
7486
// make sure we close this no matter what.
7587
defer lis.Close()

gateway/core/corehttp/gateway_handler.go

+32-56
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,16 @@ import (
1414
"strings"
1515
"time"
1616

17-
"github.com/dustin/go-humanize"
17+
humanize "github.com/dustin/go-humanize"
1818
"github.com/ipfs/go-cid"
1919
files "github.com/ipfs/go-ipfs-files"
2020
dag "github.com/ipfs/go-merkledag"
21-
"github.com/ipfs/go-mfs"
22-
"github.com/ipfs/go-path"
21+
mfs "github.com/ipfs/go-mfs"
22+
path "github.com/ipfs/go-path"
2323
"github.com/ipfs/go-path/resolver"
2424
coreiface "github.com/ipfs/interface-go-ipfs-core"
2525
ipath "github.com/ipfs/interface-go-ipfs-core/path"
2626
routing "github.com/libp2p/go-libp2p-core/routing"
27-
"github.com/multiformats/go-multibase"
2827
)
2928

3029
const (
@@ -39,6 +38,25 @@ type gatewayHandler struct {
3938
api coreiface.CoreAPI
4039
}
4140

41+
// StatusResponseWriter enables us to override HTTP Status Code passed to
42+
// WriteHeader function inside of http.ServeContent. Decision is based on
43+
// presence of HTTP Headers such as Location.
44+
type statusResponseWriter struct {
45+
http.ResponseWriter
46+
}
47+
48+
func (sw *statusResponseWriter) WriteHeader(code int) {
49+
// Check if we need to adjust Status Code to account for scheduled redirect
50+
// This enables us to return payload along with HTTP 301
51+
// for subdomain redirect in web browsers while also returning body for cli
52+
// tools which do not follow redirects by default (curl, wget).
53+
redirect := sw.ResponseWriter.Header().Get("Location")
54+
if redirect != "" && code == http.StatusOK {
55+
code = http.StatusMovedPermanently
56+
}
57+
sw.ResponseWriter.WriteHeader(code)
58+
}
59+
4260
func newGatewayHandler(c GatewayConfig, api coreiface.CoreAPI) *gatewayHandler {
4361
i := &gatewayHandler{
4462
config: c,
@@ -143,17 +161,17 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
143161
}
144162
}
145163

146-
// IPNSHostnameOption might have constructed an IPNS path using the Host header.
164+
// HostnameOption might have constructed an IPNS/IPFS path using the Host header.
147165
// In this case, we need the original path for constructing redirects
148166
// and links that match the requested URL.
149167
// For example, http://example.net would become /ipns/example.net, and
150168
// the redirects and links would end up as http://example.net/ipns/example.net
151-
originalUrlPath := prefix + urlPath
152-
ipnsHostname := false
153-
if hdr := r.Header.Get("X-Ipns-Original-Path"); len(hdr) > 0 {
154-
originalUrlPath = prefix + hdr
155-
ipnsHostname = true
169+
requestURI, err := url.ParseRequestURI(r.RequestURI)
170+
if err != nil {
171+
webError(w, "failed to parse request path", err, http.StatusInternalServerError)
172+
return
156173
}
174+
originalUrlPath := prefix + requestURI.Path
157175

158176
// Service Worker registration request
159177
if r.Header.Get("Service-Worker") == "script" {
@@ -206,39 +224,6 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
206224
w.Header().Set("X-IPFS-Path", urlPath)
207225
w.Header().Set("Etag", etag)
208226

209-
// Suborigin header, sandboxes apps from each other in the browser (even
210-
// though they are served from the same gateway domain).
211-
//
212-
// Omitted if the path was treated by IPNSHostnameOption(), for example
213-
// a request for http://example.net/ would be changed to /ipns/example.net/,
214-
// which would turn into an incorrect Suborigin header.
215-
// In this case the correct thing to do is omit the header because it is already
216-
// handled correctly without a Suborigin.
217-
//
218-
// NOTE: This is not yet widely supported by browsers.
219-
if !ipnsHostname {
220-
// e.g.: 1="ipfs", 2="QmYuNaKwY...", ...
221-
pathComponents := strings.SplitN(urlPath, "/", 4)
222-
223-
var suboriginRaw []byte
224-
cidDecoded, err := cid.Decode(pathComponents[2])
225-
if err != nil {
226-
// component 2 doesn't decode with cid, so it must be a hostname
227-
suboriginRaw = []byte(strings.ToLower(pathComponents[2]))
228-
} else {
229-
suboriginRaw = cidDecoded.Bytes()
230-
}
231-
232-
base32Encoded, err := multibase.Encode(multibase.Base32, suboriginRaw)
233-
if err != nil {
234-
internalWebError(w, err)
235-
return
236-
}
237-
238-
suborigin := pathComponents[1] + "000" + strings.ToLower(base32Encoded)
239-
w.Header().Set("Suborigin", suborigin)
240-
}
241-
242227
// set these headers _after_ the error, for we may just not have it
243228
// and dont want the client to cache a 500 response...
244229
// and only if it's /ipfs!
@@ -322,10 +307,10 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
322307

323308
// construct the correct back link
324309
// https://github.com/ipfs/go-ipfs/issues/1365
325-
var backLink string = prefix + urlPath
310+
var backLink string = originalUrlPath
326311

327312
// don't go further up than /ipfs/$hash/
328-
pathSplit := path.SplitList(backLink)
313+
pathSplit := path.SplitList(urlPath)
329314
switch {
330315
// keep backlink
331316
case len(pathSplit) == 3: // url: /ipfs/$hash
@@ -342,18 +327,8 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
342327
}
343328
}
344329

345-
// strip /ipfs/$hash from backlink if IPNSHostnameOption touched the path.
346-
if ipnsHostname {
347-
backLink = prefix + "/"
348-
if len(pathSplit) > 5 {
349-
// also strip the trailing segment, because it's a backlink
350-
backLinkParts := pathSplit[3 : len(pathSplit)-2]
351-
backLink += path.Join(backLinkParts) + "/"
352-
}
353-
}
354-
355330
var hash string
356-
if !strings.HasPrefix(originalUrlPath, ipfsPathPrefix) {
331+
if !strings.HasPrefix(urlPath, ipfsPathPrefix) {
357332
hash = resolvedPath.Cid().String()
358333
}
359334

@@ -410,6 +385,7 @@ func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, nam
410385
}
411386
w.Header().Set("Content-Type", ctype)
412387

388+
w = &statusResponseWriter{w}
413389
http.ServeContent(w, req, name, modtime, content)
414390
}
415391

gateway/core/corehttp/gateway_test.go

+9-9
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
)
@@ -184,12 +184,12 @@ func TestGatewayGet(t *testing.T) {
184184
status int
185185
text string
186186
}{
187-
{"localhost:5001", "/", http.StatusNotFound, "404 page not found\n"},
188-
{"localhost:5001", "/" + k.Cid().String(), http.StatusNotFound, "404 page not found\n"},
189-
{"localhost:5001", k.String(), http.StatusOK, "fnord"},
190-
{"localhost:5001", "/ipns/nxdomain.example.com", http.StatusNotFound, "ipfs resolve -r /ipns/nxdomain.example.com: " + namesys.ErrResolveFailed.Error() + "\n"},
191-
{"localhost:5001", "/ipns/%0D%0A%0D%0Ahello", http.StatusNotFound, "ipfs resolve -r /ipns/%0D%0A%0D%0Ahello: " + namesys.ErrResolveFailed.Error() + "\n"},
192-
{"localhost:5001", "/ipns/example.com", http.StatusOK, "fnord"},
187+
{"127.0.0.1:8080", "/", http.StatusNotFound, "404 page not found\n"},
188+
{"127.0.0.1:8080", "/" + k.Cid().String(), http.StatusNotFound, "404 page not found\n"},
189+
{"127.0.0.1:8080", k.String(), http.StatusOK, "fnord"},
190+
{"127.0.0.1:8080", "/ipns/nxdomain.example.com", http.StatusNotFound, "ipfs resolve -r /ipns/nxdomain.example.com: " + namesys.ErrResolveFailed.Error() + "\n"},
191+
{"127.0.0.1:8080", "/ipns/%0D%0A%0D%0Ahello", http.StatusNotFound, "ipfs resolve -r /ipns/%0D%0A%0D%0Ahello: " + namesys.ErrResolveFailed.Error() + "\n"},
192+
{"127.0.0.1:8080", "/ipns/example.com", http.StatusOK, "fnord"},
193193
{"example.com", "/", http.StatusOK, "fnord"},
194194

195195
{"working.example.com", "/", http.StatusOK, "fnord"},
@@ -381,7 +381,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) {
381381
if !strings.Contains(s, "Index of /foo? #<'/") {
382382
t.Fatalf("expected a path in directory listing")
383383
}
384-
if !strings.Contains(s, "<a href=\"/\">") {
384+
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/./..\">") {
385385
t.Fatalf("expected backlink in directory listing")
386386
}
387387
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/file.txt\">") {
@@ -447,7 +447,7 @@ func TestIPNSHostnameBacklinks(t *testing.T) {
447447
if !strings.Contains(s, "Index of /foo? #&lt;&#39;/bar/") {
448448
t.Fatalf("expected a path in directory listing")
449449
}
450-
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/\">") {
450+
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/bar/./..\">") {
451451
t.Fatalf("expected backlink in directory listing")
452452
}
453453
if !strings.Contains(s, "<a href=\"/foo%3F%20%23%3C%27/bar/file.txt\">") {

0 commit comments

Comments
 (0)