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