Rust-native image transformation and edge caching for Axum, delivering Cloudinary-level capabilities without external services.
- Resize (
w,h), format (f=jpeg|webp|avif), quality (q=1..100) - HMAC-SHA256 URL signing and optional expiry (
t) - Local disk cache with
Cache-ControlandETag - Streaming responses and async/await throughout
- Static frontend served via
tower-http - Direct upload flow via multipart
POST /upload - Optional Prometheus metrics
IMAGEKIT_SECRET=your-secret cargo run- Open
http://127.0.0.1:8080/for the demo UI
Config defaults (see src/main.rs):
IMAGEKIT_SECRETdefaults tolocal-dev-secretmax_input_sizeis 8MB- Allowed formats:
jpeg,webp,avif - Default output format:
webp
-
GET /sign- Signs a canonical query (excluding
sig) using the server secret. - Query:
url, optionalw,h,f,q,t. - Returns
{ canonical, sig, signed_url }. - Example:
http://127.0.0.1:8080/sign?url=https://upload.wikimedia.org/wikipedia/commons/3/3f/JPEG_example_flower.jpg&w=400&f=webp&q=80
- Signs a canonical query (excluding
-
GET /img- Transforms and serves a remote image. Requires
sig(from/sign). - Query:
url, optionalw,h,f,q,t, plussig. - Caches the transformed result to disk and streams responses.
- Example flow: call
/sign, then opensigned_url.
- Transforms and serves a remote image. Requires
-
POST /upload- Transforms an uploaded local image and returns raw image bytes.
- Multipart fields:
file(required),w,h,f,q(optional). - Example:
curl -F "file=@/path/to/photo.jpg" -F w=400 -F f=webp \http://127.0.0.1:8080/upload --output out.webp
- Served at
/(frontend/index.html). - Two flows:
- "Generate & Preview": signs and loads a remote image via
GET /img. - "Upload & Preview": uploads a local file to
POST /uploadand previews the transformed result.
- "Generate & Preview": signs and loads a remote image via
- Use direct image URLs (Content-Type
image/*). Sharing pages (e.g., Google Drive share links) often return HTML and will be rejected. - Sample URLs for testing:
https://upload.wikimedia.org/wikipedia/commons/3/3f/JPEG_example_flower.jpghttps://upload.wikimedia.org/wikipedia/commons/7/77/Delete_key1.jpg
- Cache key is derived from canonical params.
- Writes include the target format’s file extension.
- Responses set
Cache-ControlandETag.
cargo testruns unit and integration tests (signature and transform).
- Input size is limited (
max_input_size) and remote content must be an image type. - If you need persistent URLs for uploaded images, extend the upload path to write to the cache and return a stable location.
sequenceDiagram
participant UI as Frontend (index.html)
participant SIGN as GET /sign
participant IMG as GET /img
participant FETCH as fetch_source
participant XFORM as transform
participant CACHE as DiskCache
UI->>SIGN: url, w, h, f, q [,t]
SIGN-->>UI: {canonical, sig, signed_url}
UI->>IMG: GET /img?…&sig=…
IMG->>CACHE: lookup(key)
alt cache hit
CACHE-->>IMG: file path
IMG-->>UI: stream image + headers
else cache miss
IMG->>FETCH: download remote bytes
FETCH-->>IMG: image bytes
IMG->>XFORM: decode → resize → encode
XFORM-->>IMG: encoded bytes
IMG->>CACHE: write(key, bytes, format)
IMG-->>UI: stream image + headers
end
sequenceDiagram
participant UI as Frontend (index.html)
participant UP as POST /upload
participant XFORM as transform
UI->>UP: multipart { file, w?, h?, f?, q? }
UP->>XFORM: decode → resize → encode
XFORM-->>UP: encoded bytes
UP-->>UI: image bytes (no-store)
src/main.rs— server entrypoint; loadsIMAGEKIT_SECRET, buildsrouter(cfg), listens on127.0.0.1:8080.src/lib.rs— route composition and handlers:handlerforGET /img(signed remote transform + caching).sign_handlerforGET /sign(returnscanonical,sig,signed_url).upload_handlerforPOST /upload(multipart file transform, returns bytes).router(config)returnsRouterwith/img,/sign,/upload, and staticServeDiron/.route(config)returns aMethodRouterconvenience for mounting/imgonly.
src/config.rs—ImageKitConfigandImageFormat(lowercase variants for serde compatibility) plus validation.src/signature.rsandsrc/security.rs— HMAC helpers, canonicalization, signing, andverify_signatureused byGET /img.src/fetch.rs—fetch_sourcefor remote downloads with size and content-type checks.src/transform.rs— core image routines:ImageBytes::decode,resize_image,encode_image.src/cache.rsandsrc/cache/—DiskCachewithkey_for,get,put,etag_for, and content-type helpers.src/handelers/— placeholder module; not used in current wiring.frontend/index.html— demo UI with two flows (“Generate & Preview” viaGET /img, and “Upload & Preview” viaPOST /upload).tests/—signature.rsandtransform.rsunit/integration tests.