Implement ETag, HEAD requests, and conditional requests

- Add ETag generation based on output content hash (first 16 chars)
- Add ContentLength to ImageResponse from cache
- Add LoadWithSize method to ContentStorage
- Add GetOutputWithSize method to Cache
- Handle HEAD requests returning headers only
- Handle If-None-Match conditional requests returning 304
- Register HEAD route for image proxy endpoint
This commit is contained in:
2026-01-08 10:08:38 -08:00
parent 4df3e44eff
commit 1f809a6fc9
5 changed files with 70 additions and 8 deletions

View File

@@ -91,15 +91,17 @@ func (s *Service) Get(ctx context.Context, req *ImageRequest) (*ImageResponse, e
if result != nil && result.Hit {
s.cache.IncrementStats(ctx, true, 0)
reader, err := s.cache.GetOutput(result.OutputHash)
reader, size, err := s.cache.GetOutputWithSize(result.OutputHash)
if err != nil {
s.log.Error("failed to get cached output", "hash", result.OutputHash, "error", err)
// Fall through to re-fetch
} else {
return &ImageResponse{
Content: reader,
ContentType: result.ContentType,
CacheStatus: CacheHit,
Content: reader,
ContentLength: size,
ContentType: result.ContentType,
CacheStatus: CacheHit,
ETag: formatETag(result.OutputHash),
}, nil
}
}
@@ -173,15 +175,17 @@ func (s *Service) fetchAndProcess(ctx context.Context, req *ImageRequest) (*Imag
}
// Serve from the cached file on disk (same path as cache hits)
reader, err := s.cache.GetOutput(outputHash)
reader, size, err := s.cache.GetOutputWithSize(outputHash)
if err != nil {
return nil, fmt.Errorf("failed to read cached output: %w", err)
}
return &ImageResponse{
Content: reader,
ContentType: processResult.ContentType,
FetchedBytes: int64(len(sourceData)),
Content: reader,
ContentLength: size,
ContentType: processResult.ContentType,
FetchedBytes: int64(len(sourceData)),
ETag: formatETag(outputHash),
}, nil
}
@@ -260,3 +264,16 @@ func extractStatusCode(err error) int {
return httpStatusInternalError
}
// etagHashLength is the number of hash characters to use for ETags.
const etagHashLength = 16
// formatETag formats a hash as a quoted ETag value.
func formatETag(hash string) string {
// Use first 16 characters of hash for a shorter but still unique ETag
if len(hash) > etagHashLength {
hash = hash[:etagHashLength]
}
return `"` + hash + `"`
}