zshot/cliDownload

Browser as a CDN

LicenseEnterprise

You can use the ZShot server to act as a caching CDN for signed browser render requests. An initial fetch renders the content in a warm browser, and identical renders are served from disk. The server also supports expiring entries upon demand.

Start the server with caching and URL signing enabled. The Docker image reads signing and the token from the environment; the cache flags append as arguments:

zshot --server
zshot --server \
  --server-bind-port 3000 \
  --server-cache \
  --server-cache-ttl 300 \
  --server-signed-url hmac-sha256:s3cr3t \
  --server-signed-url-max-age 300 \
  --server-token-auth purg3-t0k3n

Distribute a signed URL

This example server distributes signed URLs for a menu board:

SIGNED_URL=$(python3 - <<'EOF'
import hashlib, hmac, time, urllib.parse
params = {
    "url": "https://zshot-cli.com/example_assets/daves-dog-depot/#anniversary",
    "output_type": "webm",
    "video_duration": "16",
    "video_framerate": "24",
    "browser_width": "1920",
    "browser_height": "1128",
    "video_width": "1280",
    "video_height": "752",
    "wait_for": "js:menu:ready",
    "expires": str(int(time.time()) + 300),
}
canonical = "&".join(
    "%s=%s" % (k, urllib.parse.quote(v, safe=""))
    for k, v in sorted(params.items())
)
signature = hmac.new(b"s3cr3t", canonical.encode(), hashlib.sha256).hexdigest()
print("http://127.0.0.1:3000/?" + canonical + "&signature=" + signature)
EOF
)

The signs need no credentials: the signature authorizes the fetch, and an unsigned request gets 401. The request returns a WEBM capture; every repeat returns in milliseconds from the cache, marked by the age header:

curl -sD- -o board.webm "$SIGNED_URL"   # renders the board
curl -sD- -o board.webm "$SIGNED_URL"   # served from cache
HTTP/1.1 200 OK
cache-control: public, max-age=300
etag: "sha-256=:gTUKIp2NfEIBx87zKqnqs5DOOWZC+AqTzADwwZi2y/0=:"
age: 0

Entries stay fresh for --server-cache-ttl seconds.

Expire the cache

Cache entries can be purged with a DELETE of the same render arguments, sent as query parameters or a JSON body. Purging follows the GET auth rules, so an anonymous DELETE gets 401; the Bearer token from --server-token-auth authorizes it:

curl -X DELETE http://127.0.0.1:3000/ \
  -H 'Authorization: Bearer purg3-t0k3n' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://zshot-cli.com/example_assets/daves-dog-depot/#anniversary","output_type":"webm","video_duration":16,"video_framerate":24,"browser_width":1920,"browser_height":1128,"video_width":1280,"video_height":752,"wait_for":"js:menu:ready"}'

204 confirms a fresh entry was expired; a repeat returns 410 because nothing is cached. A bare DELETE / will purge the entire cache.

Expected output