承接 alengo/sulu-bunny-cdn-bundle 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

alengo/sulu-bunny-cdn-bundle

Composer 安装命令:

composer require alengo/sulu-bunny-cdn-bundle

包简介

bunny.net CDN integration for Sulu/Symfony: tag-based edge cache invalidation (CDN-Tag header + wildcard purge) wired into FOS HttpCache.

README 文档

README

bunny.net CDN integration for Sulu/Symfony websites: tag-based edge cache invalidation wired into FOS HttpCache. On every Sulu publish, both the local HttpCache kernel and the bunny.net pull zone are purged — content updates go live worldwide in seconds instead of waiting for the TTL.

Architecture

Browser ──> bunny.net pull zone ──> local HttpCache kernel (origin) ──> Symfony/Sulu
            (edge cache, CDN-Tag)   (e.g. Sulu's Psr6Store, X-Cache-Tags)

Two cache layers, both invalidated on every publish:

  • Mirrors the X-Cache-Tags response header (FOS TagListener / Sulu) into bunny.net's CDN-Tag header (BunnyCdnTagSubscriber), hashing each tag to 12 chars (xxh128) to fit bunny.net's hard 1024-byte header limit (~78 tags per response instead of ~27 raw UUIDs).
  • Registers a BunnyProxyClient (FOS proxy client: tags, URLs, full clear) plus a MultiplexerClient that forwards every invalidation to the local Symfony HttpCache kernel client and bunny.net.
  • Prepends fos_http_cache.cache_manager.custom_proxy_client, so Sulu's CacheManager purges bunny.net on publish — no project wiring needed.

The bunny.net single-tag pitfall

bunny.net stores the whole CDN-Tag header value as ONE tag — it does not split at commas or any other separator, and its purge API answers 204 even when nothing matches. Exact-match tag purges are therefore silent no-ops.

This bundle uses the working pattern (same as the Drupal bunny_cdn module): the header is written as a space-separated list with : … : guards (CDN-Tag: : a1b2c3d4e5f6 0f9e8d7c6b5a :), and purges use a wildcard pattern ({"CacheTag": "* a1b2c3d4e5f6 *"}) that matches the space-bounded tag inside the stored value.

1. Installation

composer require alengo/sulu-bunny-cdn-bundle
// config/bundles.php
Alengo\SuluBunnyCdnBundle\AlengoBunnyCdnBundle::class => ['all' => true],
# config/packages/alengo_bunny_cdn.yaml
alengo_bunny_cdn:
    enabled: false

when@prod: &bunny
    alengo_bunny_cdn:
        enabled: true
when@stage: *bunny

The credentials default to env placeholders — set them in .env.local (never commit them):

BUNNY_API_KEY=…            # ACCOUNT API key (Account → API Key), NOT a pull-zone key
BUNNY_PULL_ZONE_ID=123456  # numeric id, visible in the dashboard URL of the zone
BUNNY_SITE_BASE_URL=https://www.example.com

The env vars must be set wherever the bundle is enabled — FOS flushes the invalidation queue on console.terminate, so the proxy client is instantiated by every console command.

All options:

alengo_bunny_cdn:
    enabled: true
    api_key: '%env(BUNNY_API_KEY)%'
    pull_zone_id: '%env(BUNNY_PULL_ZONE_ID)%'
    base_url: '%env(BUNNY_SITE_BASE_URL)%'
    api_endpoint: 'https://api.bunny.net'   # override for tests/mocks
    additional_proxy_clients:               # purged alongside bunny.net
        - fos_http_cache.proxy_client.symfony

Requires the FOS symfony proxy client (or adjust additional_proxy_clients):

# config/packages/fos_http_cache.yaml
fos_http_cache:
    proxy_client:
        symfony:
            use_kernel_dispatcher: true

2. Create the pull zone (bunny.net dashboard)

CDN → Add Pull Zone

Setting Value
Name e.g. example (becomes example.b-cdn.net)
Origin Type Origin URL
Origin URL https://origin.example.com — the host that reaches the web server WITHOUT the CDN. Must be https:// — with http:// the origin's force-SSL redirect (301 to the origin host!) gets cached and served to visitors.
Pricing Zones For GDPR-sensitive sites: enable Europe only

Under Hostnames:

  • add the public domain (e.g. www.example.com) as custom hostname
  • enable Free SSL (Let's Encrypt) and Force SSL

3. Caching settings of the pull zone

Caching → General:

  • Cache Expiration Time: Respect Origin Cache-Control. Sulu sends s-maxage according to the template cache lifetime; with working tag purges the TTL can be raised later without losing freshness.
  • Query String Sort: enable.
  • Query String Vary: "Vary by Query String Parameters" with a whitelist of the parameters that actually matter (e.g. q, page, form params) — keeps utm_*, gclid & co. from fragmenting the edge cache.
  • Strip Response Cookies: DISABLE! bunny.net strips Set-Cookie from ALL responses — including uncached POSTs, not just cache hits. The admin login never receives its session cookie and every subsequent request fails with 401. Cached personalized responses are prevented by the session-bypass edge rule (§4) and the origin's private/no-cache.
  • Perma-Cache: keep disabled — tag purging does not work with it.
  • Stale Cache: "While Origin Offline" is a sensible resilience option; keep "While Updating" OFF, otherwise the first visitor after a purge is served the old version once.

4. Edge rules (mandatory!)

bunny.net does not include cookies in the cache key. Personalized or logged-in requests must therefore BYPASS the edge cache (the origin kernel cache handles them correctly). Create these rules (Pull Zone → Edge Rules → Add Edge Rule):

Rule "SessionBypass" — logged-in users:

  • Action: Override Cache Time0
  • Condition: Request Header → header Cookie → pattern *SULUSESSID* (adjust to your session cookie name)

Rule "AdminNoCache" — never cache the admin:

  • Action: Override Cache Time0
  • Condition: Request URL → matches → */admin*
  • Needed even though the origin sends private: bare redirects (e.g. Apache's /admin/admin/) carry no Cache-Control and DO get cached.

Additional bypass rules for any personalization cookies your site uses (same action, conditions Request Header: Cookie with *cookie_name* patterns, "Match Any").

⚠ Cookie condition pitfall: do NOT use the Cookie Value condition type with pattern * — it also matches requests WITHOUT the cookie, the rule then applies to ALL requests and the whole zone becomes BYPASS (nothing is cached anymore). Use Request Header: Cookie with a substring pattern — a missing header never matches a substring.

Edge rules always need at least one condition. For rules that should apply to every request, use Request URL → matches → *.

CDN-Tag stays visible to browsers: response headers starting with CDN- cannot be set/cleared via edge rules ("Header names starting with CDN- are not allowed"). The header only contains 12-char hashes and is useful for debugging — leave it.

5. Host handling & origin lockdown

If the origin vhost only serves the origin hostname (and answers the public host with 421 Misdirected Request):

  • Forward Host Header: keep DISABLED (default) — bunny.net sends the host from the Origin URL. Forwarding the public host does NOT work (421).

  • So the app still generates public URLs, present the public host to the app in the origin's .htaccess:

    <IfModule mod_headers.c>
        RequestHeader set Host "www.example.com"
    </IfModule>
  • Origin lockdown: edge rule Add Request Header (acts on requests to the origin only) → X-Origin-Verify: <secret>, condition Request URL matches *; the .htaccess rejects requests without it:

    RewriteCond %{HTTP:X-Origin-Verify} !^<secret>$
    RewriteRule ^ - [F]

    Do not commit the secret. Save the edge rule BEFORE arming the .htaccess, or you lock bunny.net out. Rotating = change both places.

6. DNS

Point the public domain as CNAME to <zone>.b-cdn.net (only after SSL for the hostname is provisioned). The origin must stay directly reachable under its own hostname (A record, own vhost with TLS).

7. Verify

# Edge cache status (2nd request should be a HIT), CDN-Tag visible
curl -sI https://www.example.com/ | grep -iE 'cdn-cache|cache-control|cdn-tag'

# With a session cookie the edge must NEVER answer from cache (BYPASS)
curl -sI -H 'Cookie: SULUSESSID=test' https://www.example.com/ | grep -i cdn-cache

# Admin: BYPASS
curl -sI https://www.example.com/admin | grep -i cdn-cache

# Origin lockdown: direct origin hit → 403
curl -sI https://origin.example.com/ | head -1

# Manual tag purge: take a 12-char hash from the CDN-Tag header — MUST be
# purged as a wildcard pattern (single-tag storage, see above):
curl -s -X POST "https://api.bunny.net/pullzone/$BUNNY_PULL_ZONE_ID/purgeCache" \
    -H "AccessKey: $BUNNY_API_KEY" -H 'Content-Type: application/json' \
    -d '{"CacheTag": "* <tag-hash> *"}' -w '%{http_code}\n'

cdn-cache legend: MISS/EXPIRED/HIT = normal caching, BYPASS = an override-cache-time-0 rule matched. MISS after a purge = entry was gone; EXPIRED = regular TTL refetch.

⚠ Verifying purges: a 204 from the API proves nothing (bunny.net returns 204 even for non-matching tags), and a "successful purge" observed after the entry's TTL has expired is just the regular refetch. Always check cdn-cachedat WITHIN the TTL of the entry.

Publish test: change + publish a page in the admin → the next request to the live URL must return the new content within seconds.

8. Operations & limits

  • Publish invalidates both layers automatically (tag-based).
  • Full flush: bin/console fos:httpcache:clear or the dashboard's "Purge Cache". Note: Symfony's cache:clear does NOT purge HTTP caches.
  • 401 "Unauthorized" from caching proxy on publish/console commands: BUNNY_API_KEY is missing/wrong — it must be the ACCOUNT API key, not a pull-zone key. Test: curl -H "AccessKey: $KEY" https://api.bunny.net/pullzone.
  • invalidateDomain() (BAN/regex) is not supported by bunny.net and is a no-op.
  • The CDN-Tag header is limited to 1024 bytes (~78 hashed tags); overflow tags are dropped cleanly — affected pages refresh via the regular TTL.
  • Wildcard purges are rate-limited by bunny.net (burst ~20, ~0.5/s refill); mass publishes may drop some purges — affected pages heal via the TTL.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-09

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固