From 0617d60f937ca2c417d3879e426e5a31b430fd14 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 19 Aug 2024 23:36:40 +0200 Subject: [PATCH] feat(gw): set Last-Modified to value from dag-pb (if present) --- CHANGELOG.md | 1 + gateway/backend_blocks.go | 12 ++++++++++++ gateway/gateway.go | 3 ++- gateway/handler.go | 15 +++++++++++---- gateway/handler_defaults.go | 8 ++++++++ 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07527a74e9..77b813ec4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The following emojis are used to highlight certain changes: ### Changed - `chunker` refactored to reduce overall memory use by reducing heap fragmentation [#649](https://github.com/ipfs/boxo/pull/649) +- `gateway` deserialized responses will have `Last-Modified` set to value from optional UnixFS 1.5 modification time field (if present in DAG) ### Removed diff --git a/gateway/backend_blocks.go b/gateway/backend_blocks.go index 42440dfcd4..d62d3d8765 100644 --- a/gateway/backend_blocks.go +++ b/gateway/backend_blocks.go @@ -156,6 +156,12 @@ func (bb *BlocksBackend) Get(ctx context.Context, path path.ImmutablePath, range return md, nil, err } + // Set modification time in ContentPathMetadata if found in dag-pb's optional mtime field (UnixFS 1.5) + mtime := f.ModTime() + if !mtime.IsZero() { + md.ModTime = mtime + } + if d, ok := f.(files.Directory); ok { dir, err := uio.NewDirectoryFromNode(bb.dagService, nd) if err != nil { @@ -231,6 +237,12 @@ func (bb *BlocksBackend) Head(ctx context.Context, path path.ImmutablePath) (Con return ContentPathMetadata{}, nil, err } + // Set modification time in ContentPathMetadata if found in dag-pb's optional mtime field (UnixFS 1.5) + mtime := fileNode.ModTime() + if !mtime.IsZero() { + md.ModTime = mtime + } + sz, err := fileNode.Size() if err != nil { return ContentPathMetadata{}, nil, err diff --git a/gateway/gateway.go b/gateway/gateway.go index dccdaf7922..d79689925f 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -209,7 +209,8 @@ type ContentPathMetadata struct { PathSegmentRoots []cid.Cid LastSegment path.ImmutablePath LastSegmentRemainder []string - ContentType string // Only used for UnixFS requests + ContentType string // Only used for UnixFS requests + ModTime time.Time // Optional, non-zero values may be present in UnixFS 1.5 DAGs } // ByteRange describes a range request within a UnixFS file. "From" and "To" mostly diff --git a/gateway/handler.go b/gateway/handler.go index 4360d21636..85eb40bede 100644 --- a/gateway/handler.go +++ b/gateway/handler.go @@ -410,18 +410,25 @@ func addCacheControlHeaders(w http.ResponseWriter, r *http.Request, contentPath } if lastMod.IsZero() { - // Otherwise, we set Last-Modified to the current time to leverage caching heuristics + // If no lastMod, set Last-Modified to the current time to leverage caching heuristics // built into modern browsers: https://github.com/ipfs/kubo/pull/8074#pullrequestreview-645196768 modtime = time.Now() } else { + // set Last-Modified to a meaningful value e.g. one read from dag-pb (UnixFS 1.5, mtime field) + // or the last time DNSLink / IPNS Record was modified / resoved or cache modtime = lastMod } + } else { w.Header().Set("Cache-Control", immutableCacheControl) - modtime = noModtime // disable Last-Modified - // TODO: consider setting Last-Modified if UnixFS V1.5 ever gets released - // with metadata: https://github.com/ipfs/kubo/issues/6920 + if lastMod.IsZero() { + // (noop) skip Last-Modified on immutable response + modtime = noModtime + } else { + // set Last-Modified to value read from dag-pb (UnixFS 1.5, mtime field) + modtime = lastMod + } } return modtime diff --git a/gateway/handler_defaults.go b/gateway/handler_defaults.go index 78e5af952a..ee4b130ee6 100644 --- a/gateway/handler_defaults.go +++ b/gateway/handler_defaults.go @@ -94,6 +94,14 @@ func (i *handler) serveDefaults(ctx context.Context, w http.ResponseWriter, r *h setIpfsRootsHeader(w, rq, &pathMetadata) + // On deserialized responses, we prefer Last-Modified from pathMetadata + // (mtime in UnixFS 1.5 DAG). This also applies to /ipns/, because value + // from dag-pb, if present, is more meaningful than lastMod inferred from + // namesys. + if !pathMetadata.ModTime.IsZero() { + rq.lastMod = pathMetadata.ModTime + } + resolvedPath := pathMetadata.LastSegment switch mc.Code(resolvedPath.RootCid().Prefix().Codec) { case mc.Json, mc.DagJson, mc.Cbor, mc.DagCbor: