-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: serve CBOR encoded DAG nodes from the gateway #8037
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
package corehttp | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"html/template" | ||
"io" | ||
|
@@ -21,6 +23,7 @@ import ( | |
"github.com/ipfs/go-cid" | ||
files "github.com/ipfs/go-ipfs-files" | ||
assets "github.com/ipfs/go-ipfs/assets" | ||
format "github.com/ipfs/go-ipld-format" | ||
dag "github.com/ipfs/go-merkledag" | ||
mfs "github.com/ipfs/go-mfs" | ||
path "github.com/ipfs/go-path" | ||
|
@@ -264,6 +267,16 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request | |
return | ||
} | ||
|
||
if resolvedPath.Cid().Prefix().Codec == cid.DagCBOR { | ||
n, err := i.api.Dag().Get(r.Context(), resolvedPath.Cid()) | ||
if err != nil { | ||
webError(w, "ipfs dag get "+escapedURLPath, err, http.StatusNotFound) | ||
return | ||
} | ||
i.serveCBOR(w, r, n) | ||
return | ||
} | ||
|
||
dr, err := i.api.Unixfs().Get(r.Context(), resolvedPath) | ||
if err != nil { | ||
webError(w, "ipfs cat "+escapedURLPath, err, http.StatusNotFound) | ||
|
@@ -309,18 +322,9 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request | |
modtime = time.Unix(1, 0) | ||
} | ||
|
||
urlFilename := r.URL.Query().Get("filename") | ||
var name string | ||
if urlFilename != "" { | ||
disposition := "inline" | ||
if r.URL.Query().Get("download") == "true" { | ||
disposition = "attachment" | ||
} | ||
utf8Name := url.PathEscape(urlFilename) | ||
asciiName := url.PathEscape(onlyAscii.ReplaceAllLiteralString(urlFilename, "_")) | ||
w.Header().Set("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"; filename*=UTF-8''%s", disposition, asciiName, utf8Name)) | ||
name = urlFilename | ||
} else { | ||
setContentDispositionHeader(r.URL.Query(), w.Header()) | ||
name := r.URL.Query().Get("filename") | ||
if name == "" { | ||
name = getFilename(urlPath) | ||
} | ||
i.serveFile(w, r, name, modtime, f) | ||
|
@@ -523,6 +527,25 @@ func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, nam | |
http.ServeContent(w, req, name, modtime, content) | ||
} | ||
|
||
func (i *gatewayHandler) serveCBOR(w http.ResponseWriter, r *http.Request, n format.Node) { | ||
w.Header().Set("Cache-Control", "public, max-age=29030400, immutable") | ||
w.Header().Set("Content-Type", "application/json") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about this. We need to make a decision on default behavior before this ships:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mikeal pointed out that
This is really unfortunate, but likely relevant concern. I was thinking maybe gateway code could consider file extension if there is one and infer Content-Type from there ? E.g. if content targeted is Although I realize this is getting uncomfortable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd be ok with streaming the bytes as IMHO we should be serving data from the gateway that is by default the easiest format to consume from a web application. JSON is that. Also, right now we have NO support, and this is significantly better than nothing and what's here is consistent with the CLI. So I'd prefer to keep what's happening here and maybe add I like @Gozala's idea but it feels like too many special cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We really should keep complexity at bay and avoid special-casing different DAG types too much.
Update: I no longer feel as strong about keeping CBOR as default, see #8037 (review) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just require the header and the querystring. The problem you’ll run into with it only being a fallback is that the user, client developer, and the meddling proxy/cache are different actors and can’t always coordinate whether or not to explicitly opt into a feature. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean we should require both? If someone uses ductape-based CDN/middleware and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point on the address bar. It would actually have inconsistent behavior based on your browser plugins because everyone with JSON viewer plugin includes the content type in browser requests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's continue in ipfs/in-web-browsers#182 |
||
setContentDispositionHeader(r.URL.Query(), w.Header()) | ||
|
||
name := r.URL.Query().Get("filename") | ||
if name == "" { | ||
name = getFilename(r.URL.Path) | ||
} | ||
modtime := time.Unix(1, 0) | ||
|
||
b, err := json.Marshal(n) | ||
if err != nil { | ||
internalWebError(w, err) | ||
return | ||
} | ||
http.ServeContent(w, r, name, modtime, bytes.NewReader(b)) | ||
} | ||
|
||
func (i *gatewayHandler) servePretty404IfPresent(w http.ResponseWriter, r *http.Request, parsedPath ipath.Path) bool { | ||
resolved404Path, ctype, err := i.searchUpTreeFor404(r, parsedPath) | ||
if err != nil { | ||
|
@@ -838,3 +861,19 @@ func fixupSuperfluousNamespace(w http.ResponseWriter, urlPath string, urlQuery s | |
ErrorMsg: fmt.Sprintf("invalid path: %q should be %q", urlPath, intendedPath.String()), | ||
}) == nil | ||
} | ||
|
||
// setContentDispositionHeader sets the Content-Disposition header if a | ||
// "filename" was included in the URL querystring. | ||
func setContentDispositionHeader(qs url.Values, h http.Header) { | ||
alanshaw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
urlFilename := qs.Get("filename") | ||
if urlFilename == "" { | ||
return | ||
} | ||
disposition := "inline" | ||
if qs.Get("download") == "true" { | ||
disposition = "attachment" | ||
} | ||
utf8Name := url.PathEscape(urlFilename) | ||
asciiName := url.PathEscape(onlyAscii.ReplaceAllLiteralString(urlFilename, "_")) | ||
h.Set("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"; filename*=UTF-8''%s", disposition, asciiName, utf8Name)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is invalid if DAG is fetched from anything other than
/ipfs/
like/ipns/
(not immutable). See gateway_handler.go#L318-L319.This may require a bigger refactor, as we want to avoid separate code paths for unixfs and cbor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.