-
Notifications
You must be signed in to change notification settings - Fork 99
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: basic support for Ipfs-Path-Affinity from IPIP-462 #592
base: main
Are you sure you want to change the base?
Changes from all commits
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -233,6 +233,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i.handlePathAffinityHints(w, r, contentPath, logger) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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. As someone not familiar with go nor this codebase, these variable names need work 😳 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. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Detect when explicit Accept header or ?format parameter are present | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
responseFormat, formatParams, err := customResponseFormat(r) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -752,7 +754,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Detect 'Cache-Control: only-if-cached' in request and return data if it is already in the local datastore. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// https://github.com/ipfs/specs/blob/main/http-gateways/PATH_GATEWAY.md#cache-control-request-header | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// https://specs.ipfs.tech/http-gateways/path-gateway/#cache-control-request-header | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (i *handler) handleOnlyIfCached(w http.ResponseWriter, r *http.Request, contentPath path.Path) bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if r.Header.Get("Cache-Control") == "only-if-cached" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if !i.backend.IsCached(r.Context(), contentPath) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -887,6 +889,103 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Detect 'Ipfs-Path-Affinity' (IPIP-462) headers in request and use values as a content | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// routing hints if passed paths are not already in the local datastore. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// These optional hints are mostly useful for trustless block requests. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// See https://github.com/ipfs/specs/pull/462 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (i *handler) handlePathAffinityHints(w http.ResponseWriter, r *http.Request, contentPath path.Path, logger *zap.SugaredLogger) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
headerName := "Ipfs-Path-Affinity" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Skip if no header | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if r.Header.Get(headerName) == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Skip if contentPath is already locally cached | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if i.backend.IsCached(r.Context(), contentPath) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Check canonical header name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// NOTE: we don't use r.Header.Get() because client can send this header more than once | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
headerValues := r.Header[headerName] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// If not found, try lowercase version. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// NOTE: this is done manually because direct key access does not come with canonicalization, like Header.Get() does | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if len(headerValues) == 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
headerValues = r.Header[strings.ToLower(headerName)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Limit the headerValues to the first 3 items (abuse protection) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if len(headerValues) > 3 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
headerValues = headerValues[:3] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Process affinity hints | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for _, headerValue := range headerValues { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Non-ascii paths are percent-encoded. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Decode if the value starts with %2F (percent-encoded '/') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if strings.HasPrefix(headerValue, "%2F") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
decodedValue, err := url.PathUnescape(headerValue) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.Debugw("skipping invalid Ipfs-Path-Affinity hint", "error", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
headerValue = decodedValue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Confirm it is a valid content path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
affinityPath, err := path.NewPath(headerValue) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.Debugw("skipping invalid Ipfs-Path-Affinity hint", "error", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by HTTP request headers Error loading related location Loading Sensitive data returned by HTTP request headers Error loading related location Loading |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Skip duplicated work if immutable affinity hint is a subset of requested immutable contentPath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// (protect against broken clients that use affinity incorrectly) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if !contentPath.Mutable() && !affinityPath.Mutable() && strings.HasPrefix(contentPath.String(), affinityPath.String()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+939
to
+941
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. The comment is a little confusing about which is a prefix of the other.
It could mean that the contentPath should be the prefix, since the subset is a more specific (longer) path:
or it could me that the affinity path is a string subset of the longer content path:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.Debugw("skipping redundant Ipfs-Path-Affinity hint", "affinity", affinityPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by HTTP request headers Error loading related location Loading Sensitive data returned by HTTP request headers Error loading related location Loading |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Process hint in background without blocking response logic for contentPath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
go func(contentPath path.Path, affinityPath path.Path, logger *zap.SugaredLogger) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var immutableAffinityPath path.ImmutablePath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.Debugw("async processing of Ipfs-Path-Affinity hint", "affinity", affinityPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by HTTP request headers Error loading related location Loading Sensitive data returned by HTTP request headers Error loading related location Loading |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if affinityPath.Mutable() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Skip work if mutable affinity hint is a subset of mutable contentPath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if contentPath.Mutable() && strings.HasPrefix(contentPath.String(), affinityPath.String()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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. Seems like hasPrefix check could be pulled out into a more semantic function since it's used at least twice. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.Debugw("skipping redundant Ipfs-Path-Affinity hint", "affinity", affinityPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by HTTP request headers Error loading related location Loading Sensitive data returned by HTTP request headers Error loading related location Loading |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
immutableAffinityPath, _, _, err = i.backend.ResolveMutable(r.Context(), affinityPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.Debugw("error while resolving mutable Ipfs-Path-Affinity hint", "affinity", affinityPath, "error", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by HTTP request headers Error loading related location Loading Sensitive data returned by HTTP request headers Error loading related location Loading Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by HTTP request headers Error loading related location Loading Sensitive data returned by HTTP request headers Error loading related location Loading |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ipath, ok := affinityPath.(path.ImmutablePath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if !ok { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
immutableAffinityPath = ipath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Skip if affinity path is already cached | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if !i.backend.IsCached(r.Context(), immutableAffinityPath) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// The intention of below code is to asynchronously preconnect | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// gateway with providers of the affinityPath in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Ipfs-Path-Affinity hint. Once connected, these peers can be | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// asked directly (via mechanism like bitswap) for blocks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// related to main request for contentPath, and retrieve them, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// even when no other routing system had them announced. If | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// original contentPath was received and returned to HTTP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// client before below get is done, the work is cancelled. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.Debugw("started async search for providers of Ipfs-Path-Affinity hint", "affinity", affinityPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by HTTP request headers Error loading related location Loading Sensitive data returned by HTTP request headers Error loading related location Loading |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_, _, err = i.backend.GetBlock(r.Context(), immutableAffinityPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.Debugw("ended async search for providers of Ipfs-Path-Affinity hint", "affinity", affinityPath, "error", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by HTTP request headers Error loading related location Loading Sensitive data returned by HTTP request headers Error loading related location Loading Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by HTTP request headers Error loading related location Loading Sensitive data returned by HTTP request headers Error loading related location Loading |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.Debugw("skipping Ipfs-Path-Affinity hint due to data being locally cached", "affinity", affinityPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failure Code scanning / CodeQL Clear-text logging of sensitive information High Sensitive data returned by HTTP request headers Error loading related location Loading Sensitive data returned by HTTP request headers Error loading related location Loading |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+969
to
+984
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. minor: Instead of
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}(contentPath, affinityPath, logger) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// getTemplateGlobalData returns the global data necessary by most templates. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
func (i *handler) getTemplateGlobalData(r *http.Request, contentPath path.Path) assets.GlobalData { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// gatewayURL is used to link to other root CIDs. THis will be blank unless | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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.
What about only calling this if it's a request for
raw
blocks or CAR files as indicated here? Or do you want to extend the scope?