check / check (push) Failing after 3s
auto is a format in the /v1/image/ path, an encrypted URL's token and the generator page. Once the signature or token is checked, pixa chooses AVIF when Accept names image/avif, else WebP when it names image/webp, else JPEG when the most specific of image/jpeg, image/* and */* allows it or Accept is absent. q=0 refuses a format; a header allowing none of the three answers 406, one that does not parse 400. The signature and token cover auto itself; the cache key and ETag use the chosen format. Answers from then on carry Vary: Accept. Model: opus-5-5
180 lines
5.1 KiB
Go
180 lines
5.1 KiB
Go
// Package imgcache provides interfaces and types for the image caching proxy.
|
|
package imgcache
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
// ErrInvalidFitMode is returned when an invalid fit mode is provided.
|
|
var ErrInvalidFitMode = errors.New("invalid fit mode")
|
|
|
|
// ImageFormat represents supported output image formats.
|
|
type ImageFormat string
|
|
|
|
// Supported image output formats.
|
|
const (
|
|
FormatOriginal ImageFormat = "orig"
|
|
FormatJPEG ImageFormat = "jpeg"
|
|
FormatPNG ImageFormat = "png"
|
|
FormatWebP ImageFormat = "webp"
|
|
FormatAVIF ImageFormat = "avif"
|
|
FormatGIF ImageFormat = "gif"
|
|
|
|
// FormatAuto stands for AVIF, WebP or JPEG, chosen for each request
|
|
// from its Accept header once the URL's signature or token has been
|
|
// checked; it is never processed or cached as itself.
|
|
FormatAuto ImageFormat = "auto"
|
|
)
|
|
|
|
// Size represents requested image dimensions
|
|
type Size struct {
|
|
Width int
|
|
Height int
|
|
}
|
|
|
|
// OriginalSize returns true if this represents "keep original size"
|
|
func (s Size) OriginalSize() bool {
|
|
return s.Width == 0 && s.Height == 0
|
|
}
|
|
|
|
// FitMode represents how to fit image into requested dimensions.
|
|
type FitMode string
|
|
|
|
// Supported image fit modes.
|
|
const (
|
|
FitCover FitMode = "cover"
|
|
FitContain FitMode = "contain"
|
|
FitFill FitMode = "fill"
|
|
FitInside FitMode = "inside"
|
|
FitOutside FitMode = "outside"
|
|
)
|
|
|
|
// ValidateFitMode checks if the given fit mode is valid.
|
|
// Returns ErrInvalidFitMode for unrecognized fit modes.
|
|
func ValidateFitMode(fit FitMode) error {
|
|
switch fit {
|
|
case FitCover, FitContain, FitFill, FitInside, FitOutside, "":
|
|
return nil
|
|
default:
|
|
return ErrInvalidFitMode
|
|
}
|
|
}
|
|
|
|
// ValidateImageRequest checks a request's width and height with
|
|
// ValidateDimension and its fit mode with ValidateFitMode. Both the plain
|
|
// /v1/image/ route and the encrypted /v1/e/ route validate through this
|
|
// function so a request from either source enforces identical bounds.
|
|
func ValidateImageRequest(req *ImageRequest) error {
|
|
err := ValidateDimension("width", req.Size.Width)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = ValidateDimension("height", req.Size.Height)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ValidateFitMode(req.FitMode)
|
|
}
|
|
|
|
// ImageRequest represents a request for a processed image
|
|
type ImageRequest struct {
|
|
// SourceHost is the origin host (e.g., "cdn.example.com")
|
|
SourceHost string
|
|
// SourcePath is the path on the origin (e.g., "/photos/cat.jpg")
|
|
SourcePath string
|
|
// SourceQuery is the optional query string for the origin URL
|
|
SourceQuery string
|
|
// Size is the requested output dimensions
|
|
Size Size
|
|
// Format is the requested output format
|
|
Format ImageFormat
|
|
// Quality is the output quality (1-100) for lossy formats
|
|
Quality int
|
|
// FitMode is how to fit the image into requested dimensions
|
|
FitMode FitMode
|
|
// Signature is the HMAC signature for non-allowlisted hosts
|
|
Signature string
|
|
// Expires is when the URL expires: the exp of a signed URL, or the expiry
|
|
// of an encrypted URL; the zero time if it has none
|
|
Expires time.Time
|
|
// AllowHTTP indicates whether HTTP (non-TLS) is allowed for this request
|
|
AllowHTTP bool
|
|
}
|
|
|
|
// SourceURL returns the full upstream URL to fetch.
|
|
// Uses http:// scheme when AllowHTTP is true, otherwise https://.
|
|
func (r *ImageRequest) SourceURL() string {
|
|
scheme := "https"
|
|
if r.AllowHTTP {
|
|
scheme = "http"
|
|
}
|
|
|
|
url := scheme + "://" + r.SourceHost + r.SourcePath
|
|
if r.SourceQuery != "" {
|
|
url += "?" + r.SourceQuery
|
|
}
|
|
|
|
return url
|
|
}
|
|
|
|
// ImageResponse represents a processed image ready to serve
|
|
type ImageResponse struct {
|
|
// Content is the image data reader
|
|
Content io.ReadCloser
|
|
// ContentLength is the size in bytes (-1 if unknown)
|
|
ContentLength int64
|
|
// ContentType is the MIME type of the response
|
|
ContentType string
|
|
// ETag is the entity tag for caching
|
|
ETag string
|
|
// LastModified is when the content was last modified
|
|
LastModified time.Time
|
|
// CacheStatus indicates HIT, MISS, or STALE
|
|
CacheStatus CacheStatus
|
|
// FetchedBytes is the number of bytes fetched from upstream (0 if cache hit)
|
|
FetchedBytes int64
|
|
}
|
|
|
|
// CacheStatus indicates whether the response was served from cache.
|
|
type CacheStatus string
|
|
|
|
// Cache status values for response headers.
|
|
const (
|
|
CacheHit CacheStatus = "HIT"
|
|
CacheMiss CacheStatus = "MISS"
|
|
CacheStale CacheStatus = "STALE"
|
|
)
|
|
|
|
// ImageCache is the main interface for the image caching proxy
|
|
type ImageCache interface {
|
|
// Get retrieves a processed image, fetching and processing if necessary
|
|
Get(ctx context.Context, req *ImageRequest) (*ImageResponse, error)
|
|
|
|
// Warm pre-fetches and caches an image without returning it
|
|
Warm(ctx context.Context, req *ImageRequest) error
|
|
|
|
// Stats returns cache statistics
|
|
Stats(ctx context.Context) (*CacheStats, error)
|
|
}
|
|
|
|
// CacheStats contains cache statistics
|
|
type CacheStats struct {
|
|
// TotalItems is the number of cached source images plus processed
|
|
// variants
|
|
TotalItems int64
|
|
// TotalSizeBytes is the total size of cached source images and
|
|
// processed variants
|
|
TotalSizeBytes int64
|
|
// HitCount is the number of cache hits
|
|
HitCount int64
|
|
// MissCount is the number of cache misses
|
|
MissCount int64
|
|
// HitRate is HitCount / (HitCount + MissCount)
|
|
HitRate float64
|
|
}
|