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:
@@ -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 + `"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user