Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ func (e *HTTPStatusError) Error() string {
// transportHeaders are headers added by the HTTP transport layer that should
// not be surfaced as cached-object metadata on responses.
var transportHeaders = []string{ //nolint:gochecknoglobals
"Content-Length",
"Date",
"Accept-Encoding",
"User-Agent",
Expand Down
28 changes: 28 additions & 0 deletions internal/cache/cachetest/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"net/http"
"os"
"strconv"
"testing"
"time"

Expand Down Expand Up @@ -57,6 +58,10 @@ func Suite(t *testing.T, newCache func(t *testing.T) cache.Cache) {
testLastModified(t, newCache(t))
})

t.Run("ContentLength", func(t *testing.T) {
testContentLength(t, newCache(t))
})

t.Run("NamespaceIsolation", func(t *testing.T) {
testNamespaceIsolation(t, newCache(t))
})
Expand Down Expand Up @@ -343,6 +348,29 @@ func testLastModified(t *testing.T, c cache.Cache) {
assert.Equal(t, explicitTime.Format(http.TimeFormat), headers2.Get("Last-Modified"))
}

func testContentLength(t *testing.T, c cache.Cache) {
defer c.Close()
ctx := t.Context()

content := []byte("hello content length")
key := cache.NewKey("test-content-length")

w, err := c.Create(ctx, key, nil, time.Hour)
assert.NoError(t, err)
_, err = w.Write(content)
assert.NoError(t, err)
assert.NoError(t, w.Close())

reader, headers, err := c.Open(ctx, key)
assert.NoError(t, err)
defer reader.Close()
assert.Equal(t, strconv.Itoa(len(content)), headers.Get("Content-Length"))

statHeaders, err := c.Stat(ctx, key)
assert.NoError(t, err)
assert.Equal(t, strconv.Itoa(len(content)), statHeaders.Get("Content-Length"))
}

func testNamespaceIsolation(t *testing.T, c cache.Cache) {
defer c.Close()
ctx := t.Context()
Expand Down
11 changes: 10 additions & 1 deletion internal/cache/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"sync/atomic"
"time"

Expand Down Expand Up @@ -234,7 +235,8 @@ func (d *Disk) Stat(ctx context.Context, key Key) (http.Header, error) {
path := d.keyToPath(d.namespace, key)
fullPath := filepath.Join(d.config.Root, path)

if _, err := os.Stat(fullPath); err != nil {
info, err := os.Stat(fullPath)
if err != nil {
return nil, errors.Errorf("failed to stat file: %w", err)
}

Expand All @@ -252,6 +254,7 @@ func (d *Disk) Stat(ctx context.Context, key Key) (http.Header, error) {
return nil, errors.Errorf("failed to get headers: %w", err)
}

headers.Set("Content-Length", strconv.FormatInt(info.Size(), 10))
return headers, nil
}

Expand Down Expand Up @@ -279,6 +282,12 @@ func (d *Disk) Open(ctx context.Context, key Key) (io.ReadCloser, http.Header, e
return nil, nil, errors.Join(errors.Errorf("failed to get headers: %w", err), f.Close())
}

finfo, err := f.Stat()
if err != nil {
return nil, nil, errors.Join(errors.Errorf("failed to stat file for size: %w", err), f.Close())
}
headers.Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))

// Reset expiration time to implement LRU
ttl := min(expiresAt.Sub(now), d.config.MaxTTL)
newExpiresAt := now.Add(ttl)
Expand Down
9 changes: 7 additions & 2 deletions internal/cache/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"maps"
"net/http"
"os"
"strconv"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -75,7 +76,9 @@ func (m *Memory) Stat(_ context.Context, key Key) (http.Header, error) {
return nil, os.ErrNotExist
}

return entry.headers, nil
headers := maps.Clone(entry.headers)
headers.Set("Content-Length", strconv.Itoa(len(entry.data)))
return headers, nil
}

func (m *Memory) Open(_ context.Context, key Key) (io.ReadCloser, http.Header, error) {
Expand All @@ -96,7 +99,9 @@ func (m *Memory) Open(_ context.Context, key Key) (io.ReadCloser, http.Header, e
return nil, nil, os.ErrNotExist
}

return io.NopCloser(bytes.NewReader(entry.data)), entry.headers, nil
headers := maps.Clone(entry.headers)
headers.Set("Content-Length", strconv.Itoa(len(entry.data)))
return io.NopCloser(bytes.NewReader(entry.data)), headers, nil
}

func (m *Memory) Create(ctx context.Context, key Key, headers http.Header, ttl time.Duration) (Writer, error) {
Expand Down
8 changes: 6 additions & 2 deletions internal/cache/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,12 @@ func (s *S3) statAndHeaders(ctx context.Context, key Key) (minio.ObjectInfo, htt
}

func (s *S3) Stat(ctx context.Context, key Key) (http.Header, error) {
_, headers, err := s.statAndHeaders(ctx, key)
return headers, err
objInfo, headers, err := s.statAndHeaders(ctx, key)
if err != nil {
return nil, err
}
headers.Set("Content-Length", strconv.FormatInt(objInfo.Size, 10))
return headers, nil
}

func (s *S3) Open(ctx context.Context, key Key) (io.ReadCloser, http.Header, error) {
Expand Down