diff --git a/gateway/handler.go b/gateway/handler.go index eba593e53..7e94d062f 100644 --- a/gateway/handler.go +++ b/gateway/handler.go @@ -170,6 +170,29 @@ func (i *handler) optionsHandler(w http.ResponseWriter, r *http.Request) { i.addUserHeaders(w) // return all custom headers (including CORS ones, if set) } +type requestData struct { + // Defined for all requests. + begin time.Time + logger *zap.SugaredLogger + contentPath ipath.Path + responseFormat string + responseParams map[string]string + + // Defined for non IPNS Record requests. + immutablePath ImmutablePath + + // Defined if resolution has already happened. + pathMetadata *ContentPathMetadata + resolvedPath *ImmutablePath +} + +func (rq *requestData) maybeResolvedPath() ImmutablePath { + if rq.resolvedPath != nil { + return *rq.resolvedPath + } + return rq.immutablePath +} + func (i *handler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) { begin := time.Now() @@ -223,25 +246,32 @@ func (i *handler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) { return } + rq := &requestData{ + begin: begin, + logger: logger, + contentPath: contentPath, + responseFormat: responseFormat, + responseParams: formatParams, + } + // IPNS Record response format can be handled now, since (1) it needs the // non-resolved mutable path, and (2) has custom If-None-Match header handling // due to custom ETag. if responseFormat == ipnsRecordResponseFormat { logger.Debugw("serving ipns record", "path", contentPath) - success = i.serveIpnsRecord(r.Context(), w, r, contentPath, begin, logger) + success = i.serveIpnsRecord(r.Context(), w, r, rq) return } - var immutableContentPath ImmutablePath if contentPath.Mutable() { - immutableContentPath, err = i.backend.ResolveMutable(r.Context(), contentPath) + rq.immutablePath, err = i.backend.ResolveMutable(r.Context(), contentPath) if err != nil { err = fmt.Errorf("failed to resolve %s: %w", debugStr(contentPath.String()), err) i.webError(w, r, err, http.StatusInternalServerError) return } } else { - immutableContentPath, err = NewImmutablePath(contentPath) + rq.immutablePath, err = NewImmutablePath(contentPath) if err != nil { err = fmt.Errorf("path was expected to be immutable, but was not %s: %w", debugStr(contentPath.String()), err) i.webError(w, r, err, http.StatusInternalServerError) @@ -254,36 +284,28 @@ func (i *handler) getOrHeadHandler(w http.ResponseWriter, r *http.Request) { // header handling due to custom ETag. if responseFormat == carResponseFormat { logger.Debugw("serving car stream", "path", contentPath) - carVersion := formatParams["version"] - success = i.serveCAR(r.Context(), w, r, immutableContentPath, contentPath, carVersion, begin) + success = i.serveCAR(r.Context(), w, r, rq) return } // Detect when If-None-Match HTTP header allows returning HTTP 304 Not Modified. - ifNoneMatchResolvedPath, handled := i.handleIfNoneMatch(w, r, responseFormat, contentPath, immutableContentPath) - if handled { + if i.handleIfNoneMatch(w, r, rq) { return } - // If we already did the path resolution no need to do it again - maybeResolvedImPath := immutableContentPath - if ifNoneMatchResolvedPath != nil { - maybeResolvedImPath = *ifNoneMatchResolvedPath - } - // Support custom response formats passed via ?format or Accept HTTP header switch responseFormat { case "", jsonResponseFormat, cborResponseFormat: - success = i.serveDefaults(r.Context(), w, r, maybeResolvedImPath, immutableContentPath, contentPath, begin, responseFormat, logger) + success = i.serveDefaults(r.Context(), w, r, rq) case rawResponseFormat: logger.Debugw("serving raw block", "path", contentPath) - success = i.serveRawBlock(r.Context(), w, r, maybeResolvedImPath, contentPath, begin) + success = i.serveRawBlock(r.Context(), w, r, rq) case tarResponseFormat: logger.Debugw("serving tar file", "path", contentPath) - success = i.serveTAR(r.Context(), w, r, maybeResolvedImPath, contentPath, begin, logger) + success = i.serveTAR(r.Context(), w, r, rq) case dagJsonResponseFormat, dagCborResponseFormat: logger.Debugw("serving codec", "path", contentPath) - success = i.serveCodec(r.Context(), w, r, maybeResolvedImPath, contentPath, begin, responseFormat) + success = i.serveCodec(r.Context(), w, r, rq) default: // catch-all for unsuported application/vnd.* err := fmt.Errorf("unsupported format %q", responseFormat) i.webError(w, r, err, http.StatusBadRequest) @@ -646,14 +668,14 @@ func debugStr(path string) string { return q } -func (i *handler) handleIfNoneMatch(w http.ResponseWriter, r *http.Request, responseFormat string, contentPath ipath.Path, imPath ImmutablePath) (*ImmutablePath, bool) { +func (i *handler) handleIfNoneMatch(w http.ResponseWriter, r *http.Request, rq *requestData) bool { // Detect when If-None-Match HTTP header allows returning HTTP 304 Not Modified if ifNoneMatch := r.Header.Get("If-None-Match"); ifNoneMatch != "" { - pathMetadata, err := i.backend.ResolvePath(r.Context(), imPath) + pathMetadata, err := i.backend.ResolvePath(r.Context(), rq.immutablePath) if err != nil { - err = fmt.Errorf("failed to resolve %s: %w", debugStr(contentPath.String()), err) + err = fmt.Errorf("failed to resolve %s: %w", debugStr(rq.contentPath.String()), err) i.webError(w, r, err, http.StatusInternalServerError) - return nil, true + return true } resolvedPath := pathMetadata.LastSegment @@ -661,25 +683,27 @@ func (i *handler) handleIfNoneMatch(w http.ResponseWriter, r *http.Request, resp // Checks against both file, dir listing, and dag index Etags. // This is an inexpensive check, and it happens before we do any I/O. - cidEtag := getEtag(r, pathCid, responseFormat) + cidEtag := getEtag(r, pathCid, rq.responseFormat) dirEtag := getDirListingEtag(pathCid) dagEtag := getDagIndexEtag(pathCid) if etagMatch(ifNoneMatch, cidEtag, dirEtag, dagEtag) { // Finish early if client already has a matching Etag w.WriteHeader(http.StatusNotModified) - return nil, true + return true } resolvedImPath, err := NewImmutablePath(resolvedPath) if err != nil { i.webError(w, r, err, http.StatusInternalServerError) - return nil, true + return true } - return &resolvedImPath, true + rq.pathMetadata = &pathMetadata + rq.resolvedPath = &resolvedImPath + return true } - return nil, false + return false } // check if request was for one of known explicit formats, diff --git a/gateway/handler_block.go b/gateway/handler_block.go index 9708a46ef..03f5c74b1 100644 --- a/gateway/handler_block.go +++ b/gateway/handler_block.go @@ -5,23 +5,25 @@ import ( "net/http" "time" - ipath "github.com/ipfs/boxo/coreiface/path" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) // serveRawBlock returns bytes behind a raw block -func (i *handler) serveRawBlock(ctx context.Context, w http.ResponseWriter, r *http.Request, imPath ImmutablePath, contentPath ipath.Path, begin time.Time) bool { - ctx, span := spanTrace(ctx, "Handler.ServeRawBlock", trace.WithAttributes(attribute.String("path", imPath.String()))) +func (i *handler) serveRawBlock(ctx context.Context, w http.ResponseWriter, r *http.Request, rq *requestData) bool { + ctx, span := spanTrace(ctx, "Handler.ServeRawBlock", trace.WithAttributes(attribute.String("path", rq.immutablePath.String()))) defer span.End() - pathMetadata, data, err := i.backend.GetBlock(ctx, imPath) - if !i.handleRequestErrors(w, r, contentPath, err) { + pathMetadata, data, err := i.backend.GetBlock(ctx, rq.maybeResolvedPath()) + if !i.handleRequestErrors(w, r, rq.contentPath, err) { return false } + if rq.pathMetadata == nil { + rq.pathMetadata = &pathMetadata + } defer data.Close() - setIpfsRootsHeader(w, pathMetadata) + setIpfsRootsHeader(w, *rq.pathMetadata) blockCid := pathMetadata.LastSegment.Cid() @@ -35,7 +37,7 @@ func (i *handler) serveRawBlock(ctx context.Context, w http.ResponseWriter, r *h setContentDispositionHeader(w, name, "attachment") // Set remaining headers - modtime := addCacheControlHeaders(w, r, contentPath, blockCid, rawResponseFormat) + modtime := addCacheControlHeaders(w, r, rq.contentPath, blockCid, rawResponseFormat) w.Header().Set("Content-Type", rawResponseFormat) w.Header().Set("X-Content-Type-Options", "nosniff") // no funny business in the browsers :^) @@ -45,7 +47,7 @@ func (i *handler) serveRawBlock(ctx context.Context, w http.ResponseWriter, r *h if dataSent { // Update metrics - i.rawBlockGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds()) + i.rawBlockGetMetric.WithLabelValues(rq.contentPath.Namespace()).Observe(time.Since(rq.begin).Seconds()) } return dataSent diff --git a/gateway/handler_car.go b/gateway/handler_car.go index 458110de0..c332bf044 100644 --- a/gateway/handler_car.go +++ b/gateway/handler_car.go @@ -10,7 +10,6 @@ import ( "time" "github.com/cespare/xxhash/v2" - ipath "github.com/ipfs/boxo/coreiface/path" "github.com/ipfs/go-cid" "go.opentelemetry.io/otel/attribute" @@ -24,14 +23,14 @@ const ( ) // serveCAR returns a CAR stream for specific DAG+selector -func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.Request, imPath ImmutablePath, contentPath ipath.Path, carVersion string, begin time.Time) bool { - ctx, span := spanTrace(ctx, "Handler.ServeCAR", trace.WithAttributes(attribute.String("path", imPath.String()))) +func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.Request, rq *requestData) bool { + ctx, span := spanTrace(ctx, "Handler.ServeCAR", trace.WithAttributes(attribute.String("path", rq.immutablePath.String()))) defer span.End() ctx, cancel := context.WithCancel(ctx) defer cancel() - switch carVersion { + switch rq.responseParams["version"] { case "": // noop, client does not care about version case "1": // noop, we support this default: @@ -46,7 +45,7 @@ func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.R return false } - rootCid, lastSegment, err := getCarRootCidAndLastSegment(imPath) + rootCid, lastSegment, err := getCarRootCidAndLastSegment(rq.immutablePath) if err != nil { i.webError(w, r, err, http.StatusInternalServerError) return false @@ -70,10 +69,10 @@ func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.R setContentDispositionHeader(w, name, "attachment") // Set Cache-Control (same logic as for a regular files) - addCacheControlHeaders(w, r, contentPath, rootCid, carResponseFormat) + addCacheControlHeaders(w, r, rq.contentPath, rootCid, carResponseFormat) // Generate the CAR Etag. - etag := getCarEtag(imPath, params, rootCid) + etag := getCarEtag(rq.immutablePath, params, rootCid) w.Header().Set("Etag", etag) // Terminate early if Etag matches. We cannot rely on handleIfNoneMatch since @@ -83,8 +82,8 @@ func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.R return false } - carFile, err := i.backend.GetCAR(ctx, imPath, params) - if !i.handleRequestErrors(w, r, contentPath, err) { + carFile, err := i.backend.GetCAR(ctx, rq.immutablePath, params) + if !i.handleRequestErrors(w, r, rq.contentPath, err) { return false } defer carFile.Close() @@ -102,7 +101,7 @@ func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.R streamErr := multierr.Combine(carErr, copyErr) if streamErr != nil { // Update fail metric - i.carStreamFailMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds()) + i.carStreamFailMetric.WithLabelValues(rq.contentPath.Namespace()).Observe(time.Since(rq.begin).Seconds()) // We return error as a trailer, however it is not something browsers can access // (https://github.com/mdn/browser-compat-data/issues/14703) @@ -113,7 +112,7 @@ func (i *handler) serveCAR(ctx context.Context, w http.ResponseWriter, r *http.R } // Update metrics - i.carStreamGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds()) + i.carStreamGetMetric.WithLabelValues(rq.contentPath.Namespace()).Observe(time.Since(rq.begin).Seconds()) return true } diff --git a/gateway/handler_codec.go b/gateway/handler_codec.go index e7e5c3869..1954cb617 100644 --- a/gateway/handler_codec.go +++ b/gateway/handler_codec.go @@ -58,20 +58,23 @@ var contentTypeToExtension = map[string]string{ dagCborResponseFormat: ".cbor", } -func (i *handler) serveCodec(ctx context.Context, w http.ResponseWriter, r *http.Request, imPath ImmutablePath, contentPath ipath.Path, begin time.Time, requestedContentType string) bool { - ctx, span := spanTrace(ctx, "Handler.ServeCodec", trace.WithAttributes(attribute.String("path", imPath.String()), attribute.String("requestedContentType", requestedContentType))) +func (i *handler) serveCodec(ctx context.Context, w http.ResponseWriter, r *http.Request, rq *requestData) bool { + ctx, span := spanTrace(ctx, "Handler.ServeCodec", trace.WithAttributes(attribute.String("path", rq.immutablePath.String()), attribute.String("requestedContentType", rq.responseFormat))) defer span.End() - pathMetadata, data, err := i.backend.GetBlock(ctx, imPath) - if !i.handleRequestErrors(w, r, contentPath, err) { + pathMetadata, data, err := i.backend.GetBlock(ctx, rq.maybeResolvedPath()) + if !i.handleRequestErrors(w, r, rq.contentPath, err) { return false } + if rq.pathMetadata == nil { + rq.pathMetadata = &pathMetadata + } defer data.Close() - setIpfsRootsHeader(w, pathMetadata) + setIpfsRootsHeader(w, *rq.pathMetadata) resolvedPath := pathMetadata.LastSegment - return i.renderCodec(ctx, w, r, resolvedPath, data, contentPath, begin, requestedContentType) + return i.renderCodec(ctx, w, r, resolvedPath, data, rq.contentPath, rq.begin, rq.responseFormat) } func (i *handler) renderCodec(ctx context.Context, w http.ResponseWriter, r *http.Request, resolvedPath ipath.Resolved, blockData io.ReadSeekCloser, contentPath ipath.Path, begin time.Time, requestedContentType string) bool { diff --git a/gateway/handler_defaults.go b/gateway/handler_defaults.go index 5ccfed537..d301ca30d 100644 --- a/gateway/handler_defaults.go +++ b/gateway/handler_defaults.go @@ -8,19 +8,16 @@ import ( "net/textproto" "strconv" "strings" - "time" "github.com/ipfs/boxo/files" mc "github.com/multiformats/go-multicodec" - ipath "github.com/ipfs/boxo/coreiface/path" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "go.uber.org/zap" ) -func (i *handler) serveDefaults(ctx context.Context, w http.ResponseWriter, r *http.Request, maybeResolvedImPath ImmutablePath, immutableContentPath ImmutablePath, contentPath ipath.Path, begin time.Time, requestedContentType string, logger *zap.SugaredLogger) bool { - ctx, span := spanTrace(ctx, "Handler.ServeDefaults", trace.WithAttributes(attribute.String("path", contentPath.String()))) +func (i *handler) serveDefaults(ctx context.Context, w http.ResponseWriter, r *http.Request, rq *requestData) bool { + ctx, span := spanTrace(ctx, "Handler.ServeDefaults", trace.WithAttributes(attribute.String("path", rq.contentPath.String()))) defer span.End() var ( @@ -35,8 +32,8 @@ func (i *handler) serveDefaults(ctx context.Context, w http.ResponseWriter, r *h switch r.Method { case http.MethodHead: var data files.Node - pathMetadata, data, err = i.backend.Head(ctx, maybeResolvedImPath) - if !i.handleRequestErrors(w, r, contentPath, err) { + pathMetadata, data, err = i.backend.Head(ctx, rq.maybeResolvedPath()) + if !i.handleRequestErrors(w, r, rq.contentPath, err) { return false } defer data.Close() @@ -65,21 +62,21 @@ func (i *handler) serveDefaults(ctx context.Context, w http.ResponseWriter, r *h // allow backend to find providers for parents, even when internal // CIDs are not announced, and will provide better key for caching // related DAGs. - pathMetadata, getResp, err = i.backend.Get(ctx, maybeResolvedImPath, ranges...) + pathMetadata, getResp, err = i.backend.Get(ctx, rq.maybeResolvedPath(), ranges...) if err != nil { - if isWebRequest(requestedContentType) { - forwardedPath, continueProcessing := i.handleWebRequestErrors(w, r, maybeResolvedImPath, immutableContentPath, contentPath, err, logger) + if isWebRequest(rq.responseFormat) { + forwardedPath, continueProcessing := i.handleWebRequestErrors(w, r, rq.maybeResolvedPath(), rq.immutablePath, rq.contentPath, err, rq.logger) if !continueProcessing { return false } pathMetadata, getResp, err = i.backend.Get(ctx, forwardedPath, ranges...) if err != nil { - err = fmt.Errorf("failed to resolve %s: %w", debugStr(contentPath.String()), err) + err = fmt.Errorf("failed to resolve %s: %w", debugStr(rq.contentPath.String()), err) i.webError(w, r, err, http.StatusInternalServerError) return false } } else { - if !i.handleRequestErrors(w, r, contentPath, err) { + if !i.handleRequestErrors(w, r, rq.contentPath, err) { return false } } @@ -97,8 +94,10 @@ func (i *handler) serveDefaults(ctx context.Context, w http.ResponseWriter, r *h return false } - // TODO: check if we have a bug when maybeResolvedImPath is resolved and i.setIpfsRootsHeader works with pathMetadata returned by Get(maybeResolvedImPath) - setIpfsRootsHeader(w, pathMetadata) + if rq.pathMetadata == nil { + rq.pathMetadata = &pathMetadata + } + setIpfsRootsHeader(w, *rq.pathMetadata) resolvedPath := pathMetadata.LastSegment switch mc.Code(resolvedPath.Cid().Prefix().Codec) { @@ -107,23 +106,23 @@ func (i *handler) serveDefaults(ctx context.Context, w http.ResponseWriter, r *h i.webError(w, r, fmt.Errorf("decoding error: data not usable as a file"), http.StatusInternalServerError) return false } - logger.Debugw("serving codec", "path", contentPath) - return i.renderCodec(r.Context(), w, r, resolvedPath, bytesResponse, contentPath, begin, requestedContentType) + rq.logger.Debugw("serving codec", "path", rq.contentPath) + return i.renderCodec(r.Context(), w, r, resolvedPath, bytesResponse, rq.contentPath, rq.begin, rq.responseFormat) default: - logger.Debugw("serving unixfs", "path", contentPath) + rq.logger.Debugw("serving unixfs", "path", rq.contentPath) ctx, span := spanTrace(ctx, "Handler.ServeUnixFS", trace.WithAttributes(attribute.String("path", resolvedPath.String()))) defer span.End() // Handling Unixfs file if bytesResponse != nil { - logger.Debugw("serving unixfs file", "path", contentPath) - return i.serveFile(ctx, w, r, resolvedPath, contentPath, bytesResponse, pathMetadata.ContentType, begin) + rq.logger.Debugw("serving unixfs file", "path", rq.contentPath) + return i.serveFile(ctx, w, r, resolvedPath, rq.contentPath, bytesResponse, pathMetadata.ContentType, rq.begin) } // Handling Unixfs directory if directoryMetadata != nil || isDirectoryHeadRequest { - logger.Debugw("serving unixfs directory", "path", contentPath) - return i.serveDirectory(ctx, w, r, resolvedPath, contentPath, isDirectoryHeadRequest, directoryMetadata, ranges, begin, logger) + rq.logger.Debugw("serving unixfs directory", "path", rq.contentPath) + return i.serveDirectory(ctx, w, r, resolvedPath, rq.contentPath, isDirectoryHeadRequest, directoryMetadata, ranges, rq.begin, rq.logger) } i.webError(w, r, fmt.Errorf("unsupported UnixFS type"), http.StatusInternalServerError) diff --git a/gateway/handler_ipns_record.go b/gateway/handler_ipns_record.go index 66023f0d3..c811f0180 100644 --- a/gateway/handler_ipns_record.go +++ b/gateway/handler_ipns_record.go @@ -10,25 +10,23 @@ import ( "time" "github.com/cespare/xxhash/v2" - ipath "github.com/ipfs/boxo/coreiface/path" "github.com/ipfs/boxo/ipns" "github.com/ipfs/go-cid" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "go.uber.org/zap" ) -func (i *handler) serveIpnsRecord(ctx context.Context, w http.ResponseWriter, r *http.Request, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) bool { - ctx, span := spanTrace(ctx, "Handler.ServeIPNSRecord", trace.WithAttributes(attribute.String("path", contentPath.String()))) +func (i *handler) serveIpnsRecord(ctx context.Context, w http.ResponseWriter, r *http.Request, rq *requestData) bool { + ctx, span := spanTrace(ctx, "Handler.ServeIPNSRecord", trace.WithAttributes(attribute.String("path", rq.contentPath.String()))) defer span.End() - if contentPath.Namespace() != "ipns" { - err := fmt.Errorf("%s is not an IPNS link", contentPath.String()) + if rq.contentPath.Namespace() != "ipns" { + err := fmt.Errorf("%s is not an IPNS link", rq.contentPath.String()) i.webError(w, r, err, http.StatusBadRequest) return false } - key := contentPath.String() + key := rq.contentPath.String() key = strings.TrimSuffix(key, "/") key = strings.TrimPrefix(key, "/ipns/") if strings.Count(key, "/") != 0 { @@ -91,7 +89,7 @@ func (i *handler) serveIpnsRecord(ctx context.Context, w http.ResponseWriter, r _, err = w.Write(rawRecord) if err == nil { // Update metrics - i.ipnsRecordGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds()) + i.ipnsRecordGetMetric.WithLabelValues(rq.contentPath.Namespace()).Observe(time.Since(rq.begin).Seconds()) return true } diff --git a/gateway/handler_tar.go b/gateway/handler_tar.go index a46bb49dd..47f1a8625 100644 --- a/gateway/handler_tar.go +++ b/gateway/handler_tar.go @@ -6,34 +6,35 @@ import ( "net/http" "time" - ipath "github.com/ipfs/boxo/coreiface/path" "github.com/ipfs/boxo/files" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "go.uber.org/zap" ) var unixEpochTime = time.Unix(0, 0) -func (i *handler) serveTAR(ctx context.Context, w http.ResponseWriter, r *http.Request, imPath ImmutablePath, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) bool { - ctx, span := spanTrace(ctx, "Handler.ServeTAR", trace.WithAttributes(attribute.String("path", imPath.String()))) +func (i *handler) serveTAR(ctx context.Context, w http.ResponseWriter, r *http.Request, rq *requestData) bool { + ctx, span := spanTrace(ctx, "Handler.ServeTAR", trace.WithAttributes(attribute.String("path", rq.immutablePath.String()))) defer span.End() ctx, cancel := context.WithCancel(ctx) defer cancel() // Get Unixfs file (or directory) - pathMetadata, file, err := i.backend.GetAll(ctx, imPath) - if !i.handleRequestErrors(w, r, contentPath, err) { + pathMetadata, file, err := i.backend.GetAll(ctx, rq.maybeResolvedPath()) + if !i.handleRequestErrors(w, r, rq.contentPath, err) { return false } + if rq.pathMetadata == nil { + rq.pathMetadata = &pathMetadata + } defer file.Close() - setIpfsRootsHeader(w, pathMetadata) + setIpfsRootsHeader(w, *rq.pathMetadata) rootCid := pathMetadata.LastSegment.Cid() // Set Cache-Control and read optional Last-Modified time - modtime := addCacheControlHeaders(w, r, contentPath, rootCid, tarResponseFormat) + modtime := addCacheControlHeaders(w, r, rq.contentPath, rootCid, tarResponseFormat) // Set Content-Disposition var name string @@ -65,7 +66,7 @@ func (i *handler) serveTAR(ctx context.Context, w http.ResponseWriter, r *http.R // The TAR has a top-level directory (or file) named by the CID. if err := tarw.WriteFile(file, rootCid.String()); err != nil { // Update fail metric - i.tarStreamFailMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds()) + i.tarStreamFailMetric.WithLabelValues(rq.contentPath.Namespace()).Observe(time.Since(rq.begin).Seconds()) w.Header().Set("X-Stream-Error", err.Error()) // Trailer headers do not work in web browsers @@ -79,6 +80,6 @@ func (i *handler) serveTAR(ctx context.Context, w http.ResponseWriter, r *http.R } // Update metrics - i.tarStreamGetMetric.WithLabelValues(contentPath.Namespace()).Observe(time.Since(begin).Seconds()) + i.tarStreamGetMetric.WithLabelValues(rq.contentPath.Namespace()).Observe(time.Since(rq.begin).Seconds()) return true }