turn off image signing to save CPU

This commit is contained in:
Devin Zuczek
2026-08-07 15:10:09 -04:00
parent 7bbbac6dc9
commit 4e578f7771
6 changed files with 66 additions and 9 deletions
+7
View File
@@ -19,6 +19,13 @@ export type Env = SharedHonoEnv & {
* requested with `?sig=p1`. Optional — when absent, responses are unsigned.
*/
IMG_SIGNING_KEY?: string
/**
* Feature flag for response signing. `?sig=p1` is honoured only when this is
* true; when false (the default) the query param is ignored and the response
* streams unsigned. Off by default because signing is this worker's dominant
* CPU cost — see the `?sig=p1` handling in `img.app.ts`.
*/
IMG_SIGNING_ENABLED?: boolean
}
/** Variables can be extended */
+12 -4
View File
@@ -257,7 +257,8 @@ app.get(
'`recflare-img` bucket; extensionless ones are `storage` uploads and come from the',
'shared `recflare-cdn` bucket under its `image/` prefix. Optional center-crop and resize',
'run through the Photon WASM codec; `?sig=p1` adds the RSA-SHA1 `Content-Signature`',
'header the client verifies against `KEY:RSA:p1.rec.net`.',
'header the client verifies against `KEY:RSA:p1.rec.net`, when the',
'`IMG_SIGNING_ENABLED` flag is on (it is off by default).',
'',
'Note that this worker only serves bytes: the image metadata the client lists (the',
'`SavedImage` records behind `/api/images/...`) lives in the `api` worker, which',
@@ -345,8 +346,10 @@ app.get(
description: [
'`p1` RSA-SHA1 signs the response body and returns it as',
'`Content-Signature: key-id=KEY:RSA:p1.rec.net; data=<base64>`. Signed over the',
'bytes actually returned, i.e. the resized body when a transform applies. Omitted',
'when the worker has no `IMG_SIGNING_KEY`.',
'bytes actually returned, i.e. the resized body when a transform applies.',
'Signing is off by default (it costs the streaming fast path): the param is',
'ignored, and no header returned, unless the worker sets `IMG_SIGNING_ENABLED`',
'and has an `IMG_SIGNING_KEY`.',
].join(' '),
schema: { type: 'string', enum: ['p1'] },
},
@@ -369,7 +372,12 @@ app.get(
const key = c.req.param('key')
if (key.includes('..')) return c.body(null, 400)
const wantsSignature = c.req.query('sig') === 'p1'
// Signing is this worker's dominant CPU cost: it forces the whole object
// through the isolate (`arrayBuffer()` instead of streaming `object.body`)
// and pays a SHA-1 over the full body plus an RSA-2048 private-key operation
// on every edge-cache miss. Nothing verifies the header today, so `?sig=p1`
// is ignored unless IMG_SIGNING_ENABLED turns it back on.
const wantsSignature = c.env.IMG_SIGNING_ENABLED === true && c.req.query('sig') === 'p1'
const transform = parseTransform(
c.req.query('width'),
c.req.query('height'),
+21 -2
View File
@@ -1,8 +1,8 @@
import { PhotonImage } from '@cf-wasm/photon'
import { env, SELF } from 'cloudflare:test'
import { createExecutionContext, env, SELF, waitOnExecutionContext } from 'cloudflare:test'
import { beforeAll, describe, expect, it } from 'vitest'
import '../../img.app'
import app from '../../img.app'
import type { Env } from '../../context'
@@ -188,6 +188,25 @@ describe('img endpoints', () => {
expect(res.headers.get('content-signature')).toBeNull()
})
it('ignores ?sig=p1 when IMG_SIGNING_ENABLED is off', async () => {
// The deployed default (see wrangler.jsonc): the param is accepted but does
// nothing, so the object streams straight from R2 instead of being buffered,
// hashed and RSA-signed. Vitest binds the flag ON, so override it here.
const ctx = createExecutionContext()
const res = await app.fetch(
new Request(`${ORIGIN}/${R2_KEY}?sig=p1`),
{ ...env, IMG_SIGNING_ENABLED: false },
ctx
)
await waitOnExecutionContext(ctx)
expect(res.status).toBe(200)
expect(res.headers.get('content-signature')).toBeNull()
// Unsigned responses keep the source etag, and the body is untouched.
expect(res.headers.get('etag')).toBeTruthy()
expect(new Uint8Array(await res.arrayBuffer())).toEqual(IMAGE_BYTES)
})
it('resizes a static asset to ?width, preserving aspect ratio', async () => {
const full = new Uint8Array(await (await SELF.fetch(`${ORIGIN}/RecCenter.jpg`)).arrayBuffer())
const original = jpegSize(full)