Skip to content

Commit bcaacdd

Browse files
author
Justin Johnson
authored
feat(gateway): _redirects file support (#8890)
#8890 ipfs/specs#290
1 parent 2549475 commit bcaacdd

File tree

10 files changed

+593
-93
lines changed

10 files changed

+593
-93
lines changed

core/coreapi/name.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ func (e *ipnsEntry) Value() path.Path {
3737
return e.value
3838
}
3939

40+
type requestContextKey string
41+
4042
// Publish announces new IPNS name and returns the new IPNS entry.
4143
func (api *NameAPI) Publish(ctx context.Context, p path.Path, opts ...caopts.NamePublishOption) (coreiface.IpnsEntry, error) {
4244
ctx, span := tracing.Span(ctx, "CoreAPI.NameAPI", "Publish", trace.WithAttributes(attribute.String("path", p.String())))
@@ -76,7 +78,7 @@ func (api *NameAPI) Publish(ctx context.Context, p path.Path, opts ...caopts.Nam
7678

7779
if options.TTL != nil {
7880
// nolint: staticcheck // non-backward compatible change
79-
ctx = context.WithValue(ctx, "ipns-publish-ttl", *options.TTL)
81+
ctx = context.WithValue(ctx, requestContextKey("ipns-publish-ttl"), *options.TTL)
8082
}
8183

8284
eol := time.Now().Add(options.ValidTime)

core/corehttp/gateway_handler.go

+47-89
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
gopath "path"
1414
"regexp"
1515
"runtime/debug"
16-
"strconv"
1716
"strings"
1817
"time"
1918

@@ -378,30 +377,18 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
378377
return
379378
}
380379

381-
// Resolve path to the final DAG node for the ETag
382-
resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath)
383-
switch err {
384-
case nil:
385-
case coreiface.ErrOffline:
386-
webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable)
387-
return
388-
default:
389-
// if Accept is text/html, see if ipfs-404.html is present
390-
if i.servePretty404IfPresent(w, r, contentPath) {
391-
logger.Debugw("serve pretty 404 if present")
392-
return
393-
}
394-
webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusBadRequest)
395-
return
396-
}
397-
398380
// Detect when explicit Accept header or ?format parameter are present
399381
responseFormat, formatParams, err := customResponseFormat(r)
400382
if err != nil {
401383
webError(w, "error while processing the Accept header", err, http.StatusBadRequest)
402384
return
403385
}
404386
trace.SpanFromContext(r.Context()).SetAttributes(attribute.String("ResponseFormat", responseFormat))
387+
388+
resolvedPath, contentPath, ok := i.handlePathResolution(w, r, responseFormat, contentPath, logger)
389+
if !ok {
390+
return
391+
}
405392
trace.SpanFromContext(r.Context()).SetAttributes(attribute.String("ResolvedPath", resolvedPath.String()))
406393

407394
// Detect when If-None-Match HTTP header allows returning HTTP 304 Not Modified
@@ -450,36 +437,6 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
450437
}
451438
}
452439

453-
func (i *gatewayHandler) servePretty404IfPresent(w http.ResponseWriter, r *http.Request, contentPath ipath.Path) bool {
454-
resolved404Path, ctype, err := i.searchUpTreeFor404(r, contentPath)
455-
if err != nil {
456-
return false
457-
}
458-
459-
dr, err := i.api.Unixfs().Get(r.Context(), resolved404Path)
460-
if err != nil {
461-
return false
462-
}
463-
defer dr.Close()
464-
465-
f, ok := dr.(files.File)
466-
if !ok {
467-
return false
468-
}
469-
470-
size, err := f.Size()
471-
if err != nil {
472-
return false
473-
}
474-
475-
log.Debugw("using pretty 404 file", "path", contentPath)
476-
w.Header().Set("Content-Type", ctype)
477-
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
478-
w.WriteHeader(http.StatusNotFound)
479-
_, err = io.CopyN(w, f, size)
480-
return err == nil
481-
}
482-
483440
func (i *gatewayHandler) postHandler(w http.ResponseWriter, r *http.Request) {
484441
p, err := i.api.Unixfs().Add(r.Context(), files.NewReaderFile(r.Body))
485442
if err != nil {
@@ -920,55 +877,56 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string]
920877
return "", nil, nil
921878
}
922879

923-
func (i *gatewayHandler) searchUpTreeFor404(r *http.Request, contentPath ipath.Path) (ipath.Resolved, string, error) {
924-
filename404, ctype, err := preferred404Filename(r.Header.Values("Accept"))
925-
if err != nil {
926-
return nil, "", err
880+
// returns unquoted path with all special characters revealed as \u codes
881+
func debugStr(path string) string {
882+
q := fmt.Sprintf("%+q", path)
883+
if len(q) >= 3 {
884+
q = q[1 : len(q)-1]
927885
}
886+
return q
887+
}
928888

929-
pathComponents := strings.Split(contentPath.String(), "/")
930-
931-
for idx := len(pathComponents); idx >= 3; idx-- {
932-
pretty404 := gopath.Join(append(pathComponents[0:idx], filename404)...)
933-
parsed404Path := ipath.New("/" + pretty404)
934-
if parsed404Path.IsValid() != nil {
935-
break
936-
}
937-
resolvedPath, err := i.api.ResolvePath(r.Context(), parsed404Path)
938-
if err != nil {
939-
continue
940-
}
941-
return resolvedPath, ctype, nil
942-
}
889+
// Resolve the provided contentPath including any special handling related to
890+
// the requested responseFormat. Returned ok flag indicates if gateway handler
891+
// should continue processing the request.
892+
func (i *gatewayHandler) handlePathResolution(w http.ResponseWriter, r *http.Request, responseFormat string, contentPath ipath.Path, logger *zap.SugaredLogger) (resolvedPath ipath.Resolved, newContentPath ipath.Path, ok bool) {
893+
// Attempt to resolve the provided path.
894+
resolvedPath, err := i.api.ResolvePath(r.Context(), contentPath)
943895

944-
return nil, "", fmt.Errorf("no pretty 404 in any parent folder")
945-
}
896+
switch err {
897+
case nil:
898+
return resolvedPath, contentPath, true
899+
case coreiface.ErrOffline:
900+
webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusServiceUnavailable)
901+
return nil, nil, false
902+
default:
903+
// The path can't be resolved.
904+
if isUnixfsResponseFormat(responseFormat) {
905+
// If we have origin isolation (subdomain gw, DNSLink website),
906+
// and response type is UnixFS (default for website hosting)
907+
// check for presence of _redirects file and apply rules defined there.
908+
// See: https://github.com/ipfs/specs/pull/290
909+
if hasOriginIsolation(r) {
910+
resolvedPath, newContentPath, ok, hadMatchingRule := i.serveRedirectsIfPresent(w, r, resolvedPath, contentPath, logger)
911+
if hadMatchingRule {
912+
logger.Debugw("applied a rule from _redirects file")
913+
return resolvedPath, newContentPath, ok
914+
}
915+
}
946916

947-
func preferred404Filename(acceptHeaders []string) (string, string, error) {
948-
// If we ever want to offer a 404 file for a different content type
949-
// then this function will need to parse q weightings, but for now
950-
// the presence of anything matching HTML is enough.
951-
for _, acceptHeader := range acceptHeaders {
952-
accepted := strings.Split(acceptHeader, ",")
953-
for _, spec := range accepted {
954-
contentType := strings.SplitN(spec, ";", 1)[0]
955-
switch contentType {
956-
case "*/*", "text/*", "text/html":
957-
return "ipfs-404.html", "text/html", nil
917+
// if Accept is text/html, see if ipfs-404.html is present
918+
// This logic isn't documented and will likely be removed at some point.
919+
// Any 404 logic in _redirects above will have already run by this time, so it's really an extra fall back
920+
if i.serveLegacy404IfPresent(w, r, contentPath) {
921+
logger.Debugw("served legacy 404")
922+
return nil, nil, false
958923
}
959924
}
960-
}
961925

962-
return "", "", fmt.Errorf("there is no 404 file for the requested content types")
963-
}
964-
965-
// returns unquoted path with all special characters revealed as \u codes
966-
func debugStr(path string) string {
967-
q := fmt.Sprintf("%+q", path)
968-
if len(q) >= 3 {
969-
q = q[1 : len(q)-1]
926+
// Note: webError will replace http.StatusBadRequest with StatusNotFound if necessary
927+
webError(w, "ipfs resolve -r "+debugStr(contentPath.String()), err, http.StatusBadRequest)
928+
return nil, nil, false
970929
}
971-
return q
972930
}
973931

974932
// Detect 'Cache-Control: only-if-cached' in request and return data if it is already in the local datastore.

0 commit comments

Comments
 (0)