1 Commits
Author SHA1 Message Date
clawbot ce4df5077b Split internal/imgcache into the image proxy and the cache (closes #317)
check / check (push) Canceled after 0s
The new package internal/imageproxy holds Proxy, which was imgcache.Service,
with its Config, ImageResponse, CacheStatus and errors. NewProxy makes and
registers the counters of upstream fetches and transforms. internal/imgcache
holds the cache only, with its hit and miss counters, and imgcache.CacheConfig
is imgcache.Config. The cache reads and writes each kind it keeps with one pair
of methods: Variant and PutVariant, Source and PutSource, Failure and
PutFailure. Source replaces LookupSource followed by GetSourceContent.
imgcache.go and module.go are gone. The proxy's tests moved with it. The
cache's tests check that the reconciliation pass removes an empty or cut file
and its row. The proxy's tests remove them by hand, then check that Get fetches
or converts again.

Model: opus-5-5
2026-10-26 18:39:15 +00:00
11 changed files with 36 additions and 35 deletions
+5 -5
View File
@@ -33,8 +33,8 @@ P2: security: per-origin rate limiting
- 2026-10-26 `internal/imgcache` holds the cache only (closes #317). The new
package `internal/imageproxy` fetches, transforms, caches and serves images.
It imports `imgcache`, and `imgcache` does not import it.
- `imgcache.Service` is `imageproxy.Proxy`, built by `imageproxy.New` from
an `imageproxy.Config`.
- `imgcache.Service` is `imageproxy.Proxy`, built by `imageproxy.NewProxy`
from an `imageproxy.Config`.
- `ImageResponse`, `CacheStatus`, `ErrNegativeCached` and
`ErrProcessingTimedOut` moved with it, under their names.
- `imgcache.CacheConfig` is `imgcache.Config`.
@@ -49,9 +49,9 @@ P2: security: per-origin rate limiting
- `Variant` and `Source` each open the file and hand its access time to the
background writer. A cache hit still makes no database call.
- `imgcache.go` and `module.go` are gone.
- `imageproxy.New` makes the counters of upstream fetches and transforms,
and registers them on the registry it is given. The cache keeps only the
hit and miss counters.
- `NewProxy` makes the counters of upstream fetches and transforms, and
registers them on the registry it is given. The cache keeps only the hit
and miss counters.
- A cached source whose row cannot be read is logged as "failed to load
cached source, fetching". It was logged as "source lookup failed".
- The proxy's tests moved to `internal/imageproxy`. They reach the cache
+2 -2
View File
@@ -132,7 +132,7 @@ func newCountingDatabaseServer(
tb.Fatalf("imgcache.NewCache() error = %v", err)
}
svc, err := imageproxy.New(&imageproxy.Config{
svc, err := imageproxy.NewProxy(&imageproxy.Config{
Cache: cache,
Fetcher: newMockFetcher(fstest.MapFS{
countingHost + photoPath: &fstest.MapFile{Data: photo.Bytes()},
@@ -145,7 +145,7 @@ func newCountingDatabaseServer(
Logger: slog.New(slog.DiscardHandler),
}, prometheus.NewRegistry())
if err != nil {
tb.Fatalf("imageproxy.New() error = %v", err)
tb.Fatalf("imageproxy.NewProxy() error = %v", err)
}
keys, err := seal.DeriveKeys(testSigningKey)
@@ -48,7 +48,7 @@ func newRoutesFetchingThrough(
t.Fatalf("imgcache.NewCache() error = %v", err)
}
svc, err := imageproxy.New(&imageproxy.Config{
svc, err := imageproxy.NewProxy(&imageproxy.Config{
Cache: cache,
Fetcher: fetcher,
Processor: imageprocessor.New(imageprocessor.Params{
@@ -59,7 +59,7 @@ func newRoutesFetchingThrough(
Logger: log,
}, prometheus.NewRegistry())
if err != nil {
t.Fatalf("imageproxy.New() error = %v", err)
t.Fatalf("imageproxy.NewProxy() error = %v", err)
}
keys, err := seal.DeriveKeys(testSigningKey)
+1 -1
View File
@@ -59,7 +59,7 @@ func setupTestHandler(t *testing.T) *testFixtures {
t.Fatalf("failed to create cache: %v", err)
}
svc, err := imageproxy.New(&imageproxy.Config{
svc, err := imageproxy.NewProxy(&imageproxy.Config{
Cache: cache,
Fetcher: newMockFetcher(mockFS),
Processor: imageprocessor.New(imageprocessor.Params{
@@ -47,7 +47,7 @@ func newSignedHostServer(
jpegData := generateTestJPEG(t, 100, 100, color.RGBA{255, 0, 0, 255})
svc, err := imageproxy.New(&imageproxy.Config{
svc, err := imageproxy.NewProxy(&imageproxy.Config{
Cache: cache,
Fetcher: newMockFetcher(fstest.MapFS{
signedHost + photoPath: &fstest.MapFile{Data: jpegData},
@@ -60,7 +60,7 @@ func newSignedHostServer(
Logger: log,
}, prometheus.NewRegistry())
if err != nil {
t.Fatalf("imageproxy.New() error = %v", err)
t.Fatalf("imageproxy.NewProxy() error = %v", err)
}
keys, err := seal.DeriveKeys(testSigningKey)
@@ -46,7 +46,7 @@ func newImageRoute(t *testing.T, fetcher httpfetcher.Fetcher) http.Handler {
t.Fatalf("failed to create cache: %v", err)
}
svc, err := imageproxy.New(&imageproxy.Config{
svc, err := imageproxy.NewProxy(&imageproxy.Config{
Cache: cache,
Fetcher: fetcher,
Processor: imageprocessor.New(imageprocessor.Params{
@@ -62,7 +62,7 @@ func newRefererRoutes(t *testing.T) (http.Handler, *Handlers, *countingFetcher)
t.Fatalf("imgcache.NewCache() error = %v", err)
}
svc, err := imageproxy.New(&imageproxy.Config{
svc, err := imageproxy.NewProxy(&imageproxy.Config{
Cache: cache,
Fetcher: fetcher,
Processor: imageprocessor.New(imageprocessor.Params{
@@ -72,7 +72,7 @@ func newRefererRoutes(t *testing.T) (http.Handler, *Handlers, *countingFetcher)
MaxResponseSize: 50 << 20,
}, prometheus.NewRegistry())
if err != nil {
t.Fatalf("imageproxy.New() error = %v", err)
t.Fatalf("imageproxy.NewProxy() error = %v", err)
}
keys, err := seal.DeriveKeys(testSigningKey)
+16 -15
View File
@@ -9,10 +9,11 @@ import (
"sneak.berlin/go/pixa/internal/imgcache"
)
// TestNew_RegistersItsCountersOnItsRegistry checks that New registers the
// proxy's counters on the registry it is given, under the names README.md
// gives them. The cache registers its own counters on another registry.
func TestNew_RegistersItsCountersOnItsRegistry(t *testing.T) {
// TestNewProxy_RegistersItsCountersOnItsRegistry checks that NewProxy
// registers the proxy's counters on the registry it is given, under the
// names README.md gives them. The cache registers its own counters on
// another registry.
func TestNewProxy_RegistersItsCountersOnItsRegistry(t *testing.T) {
t.Parallel()
cache, err := imgcache.NewCache(setupTestDB(t),
@@ -24,9 +25,9 @@ func TestNew_RegistersItsCountersOnItsRegistry(t *testing.T) {
registry := prometheus.NewRegistry()
_, err = New(&Config{Cache: cache}, registry)
_, err = NewProxy(&Config{Cache: cache}, registry)
if err != nil {
t.Fatalf("New() error = %v", err)
t.Fatalf("NewProxy() error = %v", err)
}
names := []string{
@@ -46,10 +47,10 @@ func TestNew_RegistersItsCountersOnItsRegistry(t *testing.T) {
}
}
// TestNew_FailsWhenItsCountersCannotBeRegistered makes a second proxy on
// the registry of a first. Its counters have the same names, so the
// registry refuses them, and New must return that error.
func TestNew_FailsWhenItsCountersCannotBeRegistered(t *testing.T) {
// TestNewProxy_FailsWhenItsCountersCannotBeRegistered makes a second proxy
// on the registry of a first. Its counters have the same names, so the
// registry refuses them, and NewProxy must return that error.
func TestNewProxy_FailsWhenItsCountersCannotBeRegistered(t *testing.T) {
t.Parallel()
cache, err := imgcache.NewCache(setupTestDB(t),
@@ -61,17 +62,17 @@ func TestNew_FailsWhenItsCountersCannotBeRegistered(t *testing.T) {
registry := prometheus.NewRegistry()
_, err = New(&Config{Cache: cache}, registry)
_, err = NewProxy(&Config{Cache: cache}, registry)
if err != nil {
t.Fatalf("New() of the first proxy error = %v", err)
t.Fatalf("NewProxy() of the first proxy error = %v", err)
}
_, err = New(&Config{Cache: cache}, registry)
t.Logf("New() of the second proxy error = %v", err)
_, err = NewProxy(&Config{Cache: cache}, registry)
t.Logf("NewProxy() of the second proxy error = %v", err)
var alreadyRegistered prometheus.AlreadyRegisteredError
if !errors.As(err, &alreadyRegistered) {
t.Errorf("New() of the second proxy error = %v, want %T",
t.Errorf("NewProxy() of the second proxy error = %v, want %T",
err, alreadyRegistered)
}
}
+3 -3
View File
@@ -37,7 +37,7 @@ type Proxy struct {
variantsInProgress singleflight.Group
// The counts of upstream fetches and transforms, made and registered by
// New. They are kept in memory, so adding to one waits for nothing.
// NewProxy. They are kept in memory, so adding to one waits for nothing.
upstreamFetches prometheus.Counter
upstreamFetchBytes prometheus.Counter
transforms prometheus.Counter
@@ -61,11 +61,11 @@ type Config struct {
// errCacheRequired is returned when the proxy is built without a cache.
var errCacheRequired = errors.New("cache is required")
// New creates a new image proxy and registers its counters on
// NewProxy creates a new image proxy and registers its counters on
// metricsRegistry. pixad passes Prometheus' default registry, the one
// /metrics serves. It fails when metricsRegistry already holds counters of
// the same names.
func New(
func NewProxy(
cfg *Config, metricsRegistry prometheus.Registerer,
) (*Proxy, error) {
if cfg.Cache == nil {
@@ -199,7 +199,7 @@ func setupTestProxy(t *testing.T) (*testProxy, *TestFixtures) {
t.Fatalf("failed to create cache: %v", err)
}
proxy, err := New(&Config{
proxy, err := NewProxy(&Config{
Cache: cache,
Fetcher: httpfetcher.NewMock(mockFS),
Processor: imageprocessor.New(imageprocessor.Params{
+1 -1
View File
@@ -227,7 +227,7 @@ type imageServiceParams struct {
func newImageService(
_ fx.Lifecycle, params imageServiceParams,
) (*imageproxy.Proxy, error) {
return imageproxy.New(&imageproxy.Config{
return imageproxy.NewProxy(&imageproxy.Config{
Cache: params.Cache,
Fetcher: params.Fetcher,
Processor: params.Processor,