diff --git a/internal/cache/api.go b/internal/cache/api.go index eb9dad3..1df8b88 100644 --- a/internal/cache/api.go +++ b/internal/cache/api.go @@ -6,6 +6,7 @@ import ( "crypto/sha256" "encoding/hex" "io" + "net/textproto" "time" "github.com/alecthomas/errors" @@ -70,6 +71,21 @@ func (k *Key) MarshalText() ([]byte, error) { return []byte(k.String()), nil } +// FilterTransportHeaders returns a copy of the given headers with standard HTTP transport headers removed. +// These headers are typically added by HTTP clients/servers and should not be cached. +func FilterTransportHeaders(headers textproto.MIMEHeader) textproto.MIMEHeader { + filtered := make(textproto.MIMEHeader) + for key, values := range headers { + // Skip standard HTTP headers added by transport layer or that shouldn't be cached + if key == "Content-Length" || key == "Date" || key == "Accept-Encoding" || + key == "User-Agent" || key == "Transfer-Encoding" || key == "Time-To-Live" { + continue + } + filtered[key] = values + } + return filtered +} + // A Cache knows how to retrieve, create and delete objects from a cache. type Cache interface { // String describes the Cache implementation. @@ -78,13 +94,13 @@ type Cache interface { // // Expired files SHOULD not be returned. // Must return os.ErrNotExist if the file does not exist. - Open(ctx context.Context, key Key) (io.ReadCloser, error) + Open(ctx context.Context, key Key) (io.ReadCloser, textproto.MIMEHeader, error) // Create a new file in the cache. // // If "ttl" is zero, a maximum TTL MUST be used by the implementation. // // The file MUST not be available for read until completely written and closed. - Create(ctx context.Context, key Key, ttl time.Duration) (io.WriteCloser, error) + Create(ctx context.Context, key Key, headers textproto.MIMEHeader, ttl time.Duration) (io.WriteCloser, error) // Delete a file from the cache. // // MUST be atomic. diff --git a/internal/cache/cachetest/suite.go b/internal/cache/cachetest/suite.go index 5ec7201..80db90a 100644 --- a/internal/cache/cachetest/suite.go +++ b/internal/cache/cachetest/suite.go @@ -2,6 +2,7 @@ package cachetest import ( "io" + "net/textproto" "os" "testing" "time" @@ -41,6 +42,10 @@ func Suite(t *testing.T, newCache func(t *testing.T) cache.Cache) { t.Run("NotAvailableUntilClosed", func(t *testing.T) { testNotAvailableUntilClosed(t, newCache(t)) }) + + t.Run("Headers", func(t *testing.T) { + testHeaders(t, newCache(t)) + }) } func testCreateAndOpen(t *testing.T, c cache.Cache) { @@ -49,7 +54,7 @@ func testCreateAndOpen(t *testing.T, c cache.Cache) { key := cache.NewKey("test-key") - writer, err := c.Create(ctx, key, time.Hour) + writer, err := c.Create(ctx, key, nil, time.Hour) assert.NoError(t, err) _, err = writer.Write([]byte("hello world")) @@ -58,7 +63,7 @@ func testCreateAndOpen(t *testing.T, c cache.Cache) { err = writer.Close() assert.NoError(t, err) - reader, err := c.Open(ctx, key) + reader, _, err := c.Open(ctx, key) assert.NoError(t, err) defer reader.Close() @@ -73,7 +78,7 @@ func testNotFound(t *testing.T, c cache.Cache) { key := cache.NewKey("nonexistent") - _, err := c.Open(ctx, key) + _, _, err := c.Open(ctx, key) assert.IsError(t, err, os.ErrNotExist) } @@ -83,7 +88,7 @@ func testExpiration(t *testing.T, c cache.Cache) { key := cache.NewKey("test-key") - writer, err := c.Create(ctx, key, 10*time.Millisecond) + writer, err := c.Create(ctx, key, nil, 10*time.Millisecond) assert.NoError(t, err) _, err = writer.Write([]byte("test data")) @@ -92,13 +97,13 @@ func testExpiration(t *testing.T, c cache.Cache) { err = writer.Close() assert.NoError(t, err) - reader, err := c.Open(ctx, key) + reader, _, err := c.Open(ctx, key) assert.NoError(t, err) assert.NoError(t, reader.Close()) time.Sleep(20 * time.Millisecond) - _, err = c.Open(ctx, key) + _, _, err = c.Open(ctx, key) assert.IsError(t, err, os.ErrNotExist) } @@ -108,7 +113,7 @@ func testDefaultTTL(t *testing.T, c cache.Cache) { key := cache.NewKey("test-key") - writer, err := c.Create(ctx, key, 0) + writer, err := c.Create(ctx, key, nil, 0) assert.NoError(t, err) _, err = writer.Write([]byte("test data")) @@ -117,7 +122,7 @@ func testDefaultTTL(t *testing.T, c cache.Cache) { err = writer.Close() assert.NoError(t, err) - reader, err := c.Open(ctx, key) + reader, _, err := c.Open(ctx, key) assert.NoError(t, err) assert.NoError(t, reader.Close()) } @@ -128,7 +133,7 @@ func testDelete(t *testing.T, c cache.Cache) { key := cache.NewKey("test-key") - writer, err := c.Create(ctx, key, time.Hour) + writer, err := c.Create(ctx, key, nil, time.Hour) assert.NoError(t, err) _, err = writer.Write([]byte("test data")) @@ -140,7 +145,7 @@ func testDelete(t *testing.T, c cache.Cache) { err = c.Delete(ctx, key) assert.NoError(t, err) - _, err = c.Open(ctx, key) + _, _, err = c.Open(ctx, key) assert.IsError(t, err, os.ErrNotExist) } @@ -150,7 +155,7 @@ func testMultipleWrites(t *testing.T, c cache.Cache) { key := cache.NewKey("test-key") - writer, err := c.Create(ctx, key, time.Hour) + writer, err := c.Create(ctx, key, nil, time.Hour) assert.NoError(t, err) _, err = writer.Write([]byte("hello ")) @@ -162,7 +167,7 @@ func testMultipleWrites(t *testing.T, c cache.Cache) { err = writer.Close() assert.NoError(t, err) - reader, err := c.Open(ctx, key) + reader, _, err := c.Open(ctx, key) assert.NoError(t, err) defer reader.Close() @@ -177,18 +182,54 @@ func testNotAvailableUntilClosed(t *testing.T, c cache.Cache) { key := cache.NewKey("test-key") - writer, err := c.Create(ctx, key, time.Hour) + writer, err := c.Create(ctx, key, nil, time.Hour) assert.NoError(t, err) _, err = writer.Write([]byte("test data")) assert.NoError(t, err) - _, err = c.Open(ctx, key) + _, _, err = c.Open(ctx, key) assert.IsError(t, err, os.ErrNotExist) err = writer.Close() assert.NoError(t, err) - _, err = c.Open(ctx, key) + _, _, err = c.Open(ctx, key) + assert.NoError(t, err) +} + +func testHeaders(t *testing.T, c cache.Cache) { + defer c.Close() + ctx := t.Context() + + key := cache.NewKey("test-key-with-headers") + + // Create headers to store + headers := textproto.MIMEHeader{ + "Content-Type": []string{"application/json"}, + "Cache-Control": []string{"max-age=3600"}, + "X-Custom-Field": []string{"custom-value"}, + } + + writer, err := c.Create(ctx, key, headers, time.Hour) + assert.NoError(t, err) + + _, err = writer.Write([]byte("test data with headers")) + assert.NoError(t, err) + + err = writer.Close() + assert.NoError(t, err) + + // Open and verify headers are returned + reader, returnedHeaders, err := c.Open(ctx, key) assert.NoError(t, err) + defer reader.Close() + + // Verify the data + data, err := io.ReadAll(reader) + assert.NoError(t, err) + assert.Equal(t, "test data with headers", string(data)) + + // Verify headers + assert.Equal(t, headers, returnedHeaders) } diff --git a/internal/cache/disk.go b/internal/cache/disk.go index 3797d33..70af8f2 100644 --- a/internal/cache/disk.go +++ b/internal/cache/disk.go @@ -5,6 +5,7 @@ import ( "io" "io/fs" "log/slog" + "net/textproto" "os" "path/filepath" "sort" @@ -122,7 +123,7 @@ func (d *Disk) Size() int64 { return d.size.Load() } -func (d *Disk) Create(_ context.Context, key Key, ttl time.Duration) (io.WriteCloser, error) { +func (d *Disk) Create(_ context.Context, key Key, headers textproto.MIMEHeader, ttl time.Duration) (io.WriteCloser, error) { if ttl > d.config.MaxTTL || ttl == 0 { ttl = d.config.MaxTTL } @@ -150,6 +151,7 @@ func (d *Disk) Create(_ context.Context, key Key, ttl time.Duration) (io.WriteCl path: fullPath, tempPath: tempPath, expiresAt: expiresAt, + headers: headers, }, nil } @@ -159,7 +161,7 @@ func (d *Disk) Delete(_ context.Context, key Key) error { // Check if file is expired expired := false - expiresAt, err := d.ttl.get(key) + expiresAt, _, err := d.ttl.get(key) if err == nil && time.Now().After(expiresAt) { expired = true } @@ -186,34 +188,34 @@ func (d *Disk) Delete(_ context.Context, key Key) error { return nil } -func (d *Disk) Open(ctx context.Context, key Key) (io.ReadCloser, error) { +func (d *Disk) Open(ctx context.Context, key Key) (io.ReadCloser, textproto.MIMEHeader, error) { path := d.keyToPath(key) fullPath := filepath.Join(d.config.Root, path) f, err := os.Open(fullPath) if err != nil { - return nil, errors.Errorf("failed to open file: %w", err) + return nil, nil, errors.Errorf("failed to open file: %w", err) } - expiresAt, err := d.ttl.get(key) + expiresAt, headers, err := d.ttl.get(key) if err != nil { - return nil, errors.Join(errors.Errorf("failed to get expiration time: %w", err), f.Close()) + return nil, nil, errors.Join(errors.Errorf("failed to get metadata: %w", err), f.Close()) } now := time.Now() if now.After(expiresAt) { - return nil, errors.Join(fs.ErrNotExist, f.Close(), d.Delete(ctx, key)) + return nil, nil, errors.Join(fs.ErrNotExist, f.Close(), d.Delete(ctx, key)) } // Reset expiration time to implement LRU ttl := min(expiresAt.Sub(now), d.config.MaxTTL) newExpiresAt := now.Add(ttl) - if err := d.ttl.set(key, newExpiresAt); err != nil { - return nil, errors.Join(errors.Errorf("failed to update expiration time: %w", err), f.Close()) + if err := d.ttl.set(key, newExpiresAt, headers); err != nil { + return nil, nil, errors.Join(errors.Errorf("failed to update expiration time: %w", err), f.Close()) } - return f, nil + return f, headers, nil } func (d *Disk) keyToPath(key Key) string { @@ -330,6 +332,7 @@ type diskWriter struct { path string tempPath string expiresAt time.Time + headers textproto.MIMEHeader size int64 } @@ -348,8 +351,8 @@ func (w *diskWriter) Close() error { return errors.Errorf("failed to rename temp file: %w", err) } - if err := w.disk.ttl.set(w.key, w.expiresAt); err != nil { - return errors.Join(errors.Errorf("failed to set expiration time: %w", err), os.Remove(w.path)) + if err := w.disk.ttl.set(w.key, w.expiresAt, w.headers); err != nil { + return errors.Join(errors.Errorf("failed to set metadata: %w", err), os.Remove(w.path)) } w.disk.size.Add(w.size) diff --git a/internal/cache/memory.go b/internal/cache/memory.go index dcb60c5..6c4cd0c 100644 --- a/internal/cache/memory.go +++ b/internal/cache/memory.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "net/textproto" "os" "sync" "time" @@ -24,6 +25,7 @@ type MemoryConfig struct { type memoryEntry struct { data []byte expiresAt time.Time + headers textproto.MIMEHeader } type Memory struct { @@ -42,23 +44,23 @@ func NewMemory(_ context.Context, config MemoryConfig) (*Memory, error) { func (m *Memory) String() string { return fmt.Sprintf("memory:%dMB", m.config.LimitMB) } -func (m *Memory) Open(_ context.Context, key Key) (io.ReadCloser, error) { +func (m *Memory) Open(_ context.Context, key Key) (io.ReadCloser, textproto.MIMEHeader, error) { m.mu.RLock() defer m.mu.RUnlock() entry, exists := m.entries[key] if !exists { - return nil, os.ErrNotExist + return nil, nil, os.ErrNotExist } if time.Now().After(entry.expiresAt) { - return nil, os.ErrNotExist + return nil, nil, os.ErrNotExist } - return io.NopCloser(bytes.NewReader(entry.data)), nil + return io.NopCloser(bytes.NewReader(entry.data)), entry.headers, nil } -func (m *Memory) Create(_ context.Context, key Key, ttl time.Duration) (io.WriteCloser, error) { +func (m *Memory) Create(_ context.Context, key Key, headers textproto.MIMEHeader, ttl time.Duration) (io.WriteCloser, error) { if ttl == 0 { ttl = m.config.MaxTTL } @@ -68,6 +70,7 @@ func (m *Memory) Create(_ context.Context, key Key, ttl time.Duration) (io.Write key: key, buf: &bytes.Buffer{}, expiresAt: time.Now().Add(ttl), + headers: headers, } return writer, nil @@ -99,6 +102,7 @@ type memoryWriter struct { key Key buf *bytes.Buffer expiresAt time.Time + headers textproto.MIMEHeader closed bool } @@ -139,6 +143,7 @@ func (w *memoryWriter) Close() error { w.cache.entries[w.key] = &memoryEntry{ data: w.buf.Bytes(), expiresAt: w.expiresAt, + headers: w.headers, } w.cache.currentSize += newSize diff --git a/internal/cache/remote/client.go b/internal/cache/remote/client.go index 220effe..80688ff 100644 --- a/internal/cache/remote/client.go +++ b/internal/cache/remote/client.go @@ -4,7 +4,9 @@ import ( "context" "fmt" "io" + "maps" "net/http" + "net/textproto" "os" "time" @@ -32,31 +34,34 @@ func NewClient(baseURL string) *Client { func (c *Client) String() string { return "remote:" + c.baseURL } // Open retrieves an object from the remote cache. -func (c *Client) Open(ctx context.Context, key cache.Key) (io.ReadCloser, error) { +func (c *Client) Open(ctx context.Context, key cache.Key) (io.ReadCloser, textproto.MIMEHeader, error) { url := fmt.Sprintf("%s/%s", c.baseURL, key.String()) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { - return nil, errors.Wrap(err, "failed to create request") + return nil, nil, errors.Wrap(err, "failed to create request") } resp, err := c.client.Do(req) if err != nil { - return nil, errors.Wrap(err, "failed to execute request") + return nil, nil, errors.Wrap(err, "failed to execute request") } if resp.StatusCode == http.StatusNotFound { - return nil, errors.Join(os.ErrNotExist, resp.Body.Close()) + return nil, nil, errors.Join(os.ErrNotExist, resp.Body.Close()) } if resp.StatusCode != http.StatusOK { - return nil, errors.Join(errors.Errorf("unexpected status code: %d", resp.StatusCode), resp.Body.Close()) + return nil, nil, errors.Join(errors.Errorf("unexpected status code: %d", resp.StatusCode), resp.Body.Close()) } - return resp.Body, nil + // Filter out HTTP transport headers + headers := cache.FilterTransportHeaders(textproto.MIMEHeader(resp.Header)) + + return resp.Body, headers, nil } // Create stores a new object in the remote cache. -func (c *Client) Create(ctx context.Context, key cache.Key, ttl time.Duration) (io.WriteCloser, error) { +func (c *Client) Create(ctx context.Context, key cache.Key, headers textproto.MIMEHeader, ttl time.Duration) (io.WriteCloser, error) { pr, pw := io.Pipe() url := fmt.Sprintf("%s/%s", c.baseURL, key.String()) @@ -65,6 +70,8 @@ func (c *Client) Create(ctx context.Context, key cache.Key, ttl time.Duration) ( return nil, errors.Join(errors.Wrap(err, "failed to create request"), pr.Close(), pw.Close()) } + maps.Copy(req.Header, headers) + if ttl > 0 { req.Header.Set("Time-To-Live", ttl.String()) } diff --git a/internal/cache/ttl_storage.go b/internal/cache/ttl_storage.go index 33e6582..016c6db 100644 --- a/internal/cache/ttl_storage.go +++ b/internal/cache/ttl_storage.go @@ -1,15 +1,23 @@ package cache import ( + "encoding/json" + "net/textproto" "time" "github.com/alecthomas/errors" "go.etcd.io/bbolt" ) -var ttlBucketName = []byte("ttl") +var metadataBucketName = []byte("metadata") -// ttlStorage manages expiration times for cache entries using bbolt. +// metadata stores expiration time and headers for a cache entry. +type metadata struct { + ExpiresAt time.Time `json:"expires_at"` + Headers textproto.MIMEHeader `json:"headers"` +} + +// ttlStorage manages expiration times and headers for cache entries using bbolt. type ttlStorage struct { db *bbolt.DB } @@ -17,92 +25,78 @@ type ttlStorage struct { // newTTLStorage creates a new bbolt-backed TTL storage. func newTTLStorage(dbPath string) (*ttlStorage, error) { db, err := bbolt.Open(dbPath, 0600, &bbolt.Options{ - Timeout: 1 * time.Second, + Timeout: 5 * time.Second, }) if err != nil { return nil, errors.Errorf("failed to open bbolt database: %w", err) } // Create the bucket if it doesn't exist - err = db.Update(func(tx *bbolt.Tx) error { - _, err := tx.CreateBucketIfNotExists(ttlBucketName) + if err := db.Update(func(tx *bbolt.Tx) error { + _, err := tx.CreateBucketIfNotExists(metadataBucketName) return errors.WithStack(err) - }) - if err != nil { - return nil, errors.Join(errors.Errorf("failed to create ttl bucket: %w", err), db.Close()) + }); err != nil { + return nil, errors.Join(errors.Errorf("failed to create metadata bucket: %w", err), db.Close()) } return &ttlStorage{db: db}, nil } -func (s *ttlStorage) set(key Key, expiresAt time.Time) error { - expiresAtBytes, err := expiresAt.MarshalBinary() - if err != nil { - return errors.Errorf("failed to marshal expiration time: %w", err) +func (s *ttlStorage) set(key Key, expiresAt time.Time, headers textproto.MIMEHeader) error { + md := metadata{ + ExpiresAt: expiresAt, + Headers: headers, } - err = s.db.Update(func(tx *bbolt.Tx) error { - bucket := tx.Bucket(ttlBucketName) - return bucket.Put(key[:], expiresAtBytes) - }) + mdBytes, err := json.Marshal(md) if err != nil { - return errors.Errorf("failed to set expiration time: %w", err) + return errors.Errorf("failed to encode metadata: %w", err) } - return nil + return errors.WithStack(s.db.Update(func(tx *bbolt.Tx) error { + bucket := tx.Bucket(metadataBucketName) + return bucket.Put(key[:], mdBytes) + })) } -func (s *ttlStorage) get(key Key) (time.Time, error) { - var expiresAt time.Time - +func (s *ttlStorage) get(key Key) (time.Time, textproto.MIMEHeader, error) { + var md metadata err := s.db.View(func(tx *bbolt.Tx) error { - bucket := tx.Bucket(ttlBucketName) - expiresAtBytes := bucket.Get(key[:]) - if expiresAtBytes == nil { + bucket := tx.Bucket(metadataBucketName) + mdBytes := bucket.Get(key[:]) + if mdBytes == nil { return errors.New("key not found") } - return errors.WithStack(expiresAt.UnmarshalBinary(expiresAtBytes)) + return errors.WithStack(json.Unmarshal(mdBytes, &md)) }) - if err != nil { - return time.Time{}, errors.Errorf("failed to get expiration time: %w", err) - } - - return expiresAt, nil + return md.ExpiresAt, md.Headers, errors.WithStack(err) } func (s *ttlStorage) delete(key Key) error { - err := s.db.Update(func(tx *bbolt.Tx) error { - bucket := tx.Bucket(ttlBucketName) + return errors.WithStack(s.db.Update(func(tx *bbolt.Tx) error { + bucket := tx.Bucket(metadataBucketName) return bucket.Delete(key[:]) - }) - if err != nil { - return errors.Errorf("failed to delete expiration time: %w", err) - } - return nil + })) } func (s *ttlStorage) deleteAll(keys []Key) error { if len(keys) == 0 { return nil } - err := s.db.Update(func(tx *bbolt.Tx) error { - bucket := tx.Bucket(ttlBucketName) + return errors.WithStack(s.db.Update(func(tx *bbolt.Tx) error { + bucket := tx.Bucket(metadataBucketName) for _, key := range keys { if err := bucket.Delete(key[:]); err != nil { - return errors.Errorf("failed to delete expiration time: %w", err) + return errors.Errorf("failed to delete metadata: %w", err) } } return nil - }) - if err != nil { - return errors.Errorf("failed to delete expiration times: %w", err) - } - return nil + })) } func (s *ttlStorage) walk(fn func(key Key, expiresAt time.Time) error) error { - err := s.db.View(func(tx *bbolt.Tx) error { - bucket := tx.Bucket(ttlBucketName) + return errors.WithStack(s.db.View(func(tx *bbolt.Tx) error { + bucket := tx.Bucket(metadataBucketName) if bucket == nil { return nil } @@ -112,17 +106,13 @@ func (s *ttlStorage) walk(fn func(key Key, expiresAt time.Time) error) error { } var key Key copy(key[:], k) - var expiresAt time.Time - if err := expiresAt.UnmarshalBinary(v); err != nil { + var md metadata + if err := json.Unmarshal(v, &md); err != nil { return nil //nolint:nilerr } - return fn(key, expiresAt) + return fn(key, md.ExpiresAt) }) - }) - if err != nil { - return errors.Errorf("failed to walk TTL entries: %w", err) - } - return nil + })) } func (s *ttlStorage) close() error { diff --git a/internal/strategy/default.go b/internal/strategy/default.go index e1db9d0..9958945 100644 --- a/internal/strategy/default.go +++ b/internal/strategy/default.go @@ -5,7 +5,9 @@ import ( "errors" "io" "log/slog" + "maps" "net/http" + "net/textproto" "os" "time" @@ -55,7 +57,7 @@ func (d *Default) getObject(w http.ResponseWriter, r *http.Request) { return } - cr, err := d.cache.Open(r.Context(), key) + cr, headers, err := d.cache.Open(r.Context(), key) if err != nil { if errors.Is(err, os.ErrNotExist) { d.httpError(w, http.StatusNotFound, err, "Cache object not found", slog.String("key", key.String())) @@ -65,6 +67,8 @@ func (d *Default) getObject(w http.ResponseWriter, r *http.Request) { return } + maps.Copy(w.Header(), headers) + _, err = io.Copy(w, cr) if err != nil { d.logger.Error("Failed to copy cache object to response", slog.String("error", err.Error()), slog.String("key", key.String())) @@ -91,7 +95,10 @@ func (d *Default) putObject(w http.ResponseWriter, r *http.Request) { } } - cw, err := d.cache.Create(r.Context(), key, ttl) + // Extract and filter headers from request + headers := cache.FilterTransportHeaders(textproto.MIMEHeader(r.Header)) + + cw, err := d.cache.Create(r.Context(), key, headers, ttl) if err != nil { d.httpError(w, http.StatusInternalServerError, err, "Failed to create cache writer", slog.String("key", key.String())) return diff --git a/internal/strategy/host.go b/internal/strategy/host.go index 5188de9..86a1862 100644 --- a/internal/strategy/host.go +++ b/internal/strategy/host.go @@ -5,7 +5,9 @@ import ( "fmt" "io" "log/slog" + "maps" "net/http" + "net/textproto" "net/url" "os" @@ -70,9 +72,10 @@ func (d *Host) ServeHTTP(w http.ResponseWriter, r *http.Request) { key := cache.NewKey(fullURL) - cr, err := d.cache.Open(r.Context(), key) + cr, headers, err := d.cache.Open(r.Context(), key) if err == nil { defer cr.Close() + maps.Copy(w.Header(), headers) if _, err := io.Copy(w, cr); err != nil { d.logger.Error("Failed to copy cached response", slog.String("error", err.Error()), slog.String("url", fullURL)) } @@ -104,7 +107,8 @@ func (d *Host) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - cw, err := d.cache.Create(r.Context(), key, 0) + responseHeaders := textproto.MIMEHeader(maps.Clone(resp.Header)) + cw, err := d.cache.Create(r.Context(), key, responseHeaders, 0) if err != nil { d.httpError(w, http.StatusInternalServerError, err, "Failed to create cache entry", slog.String("url", fullURL)) return diff --git a/internal/strategy/host_test.go b/internal/strategy/host_test.go index be7abe7..45a95a3 100644 --- a/internal/strategy/host_test.go +++ b/internal/strategy/host_test.go @@ -72,7 +72,7 @@ func TestHostNonOKStatus(t *testing.T) { assert.Equal(t, "not found", w.Body.String()) key := cache.NewKey(backend.URL + "/missing") - _, err = memCache.Open(context.Background(), key) + _, _, err = memCache.Open(context.Background(), key) assert.Error(t, err, "non-OK responses should not be cached") } diff --git a/sfptc.hcl b/sfptc.hcl index 8549a0a..e585e9c 100644 --- a/sfptc.hcl +++ b/sfptc.hcl @@ -2,7 +2,7 @@ # strategy docker {} # strategy hermit {} # strategy artifactory { -# mitm = ["artifactory.global.square"] +# mitm = ["artifactory.square.com"] # } host "/github/" {