API Platform+Fastly缓存:首次PUT请求遇401 BAN错误,重试恢复正常求助
解决API Platform + Heroku + Fastly首次PUT请求401的问题
看起来你遇到的核心问题是首次PUT请求触发的Fastly BAN操作缺少JWT令牌导致401,第二次请求自动恢复,结合本地正常、线上异常的情况,我整理了几个可行的排查和解决方向:
1. 检查API Platform缓存失效请求的头部传递
API Platform默认会在资源更新时自动向缓存服务器发送BAN请求来清除对应缓存,但很可能首次请求时,这个BAN请求没有携带Fastly要求的JWT认证头部。
- 先去Heroku的日志里抓取首次PUT请求时的BAN请求详情,对比第二次请求的BAN头部,确认是否存在Authorization头部缺失的情况。
- 如果确实缺失,你可以自定义缓存失效的事件订阅器,手动把当前请求的JWT令牌添加到BAN请求的头部。比如创建一个事件订阅类:
// src/EventSubscriber/CacheInvalidationSubscriber.php namespace App\EventSubscriber; use ApiPlatform\Core\EventListener\EventPriorities; use ApiPlatform\Core\Bridge\Symfony\Bundle\EventListener\CacheInvalidationListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\ViewEvent; class CacheInvalidationSubscriber implements EventSubscriberInterface { private $cacheInvalidationListener; public function __construct(CacheInvalidationListener $cacheInvalidationListener) { $this->cacheInvalidationListener = $cacheInvalidationListener; } public static function getSubscribedEvents() { return [ ViewEvent::class => ['onView', EventPriorities::POST_WRITE], ]; } public function onView(ViewEvent $event) { $request = $event->getRequest(); // 只处理PUT/PATCH/DELETE请求 if (!in_array($request->getMethod(), [Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])) { return; } // 获取当前请求的JWT令牌 $token = $request->headers->get('Authorization'); if ($token) { // 修改缓存失效客户端的默认头部,添加JWT $client = $this->cacheInvalidationListener->getClient(); $client->setDefaultOption('headers/Authorization', $token); } // 执行原本的缓存失效逻辑 $this->cacheInvalidationListener->onKernelView($event); } }
2. 验证Fastly的BAN请求认证规则
本地环境正常,大概率是线上Fastly配置了BAN请求必须携带JWT认证,而本地的Varnish(或测试缓存)没有这个限制:
- 登录Fastly控制台,检查你的服务是否对
BAN方法设置了ACL或JWT认证规则,确认规则是否要求所有BAN请求必须携带有效的JWT令牌。 - 验证API生成的JWT令牌是否能被Fastly正确解析:可以用curl手动发送带JWT的BAN请求到Fastly地址,看是否返回200,比如:
curl -X BAN http://example.global.ssl.fastly.net -H "Authorization: Bearer YOUR_JWT_TOKEN"
如果手动请求正常,说明问题出在API Platform发送BAN请求时没有带上令牌。
3. 排查Heroku环境的请求头部传递问题
Heroku的路由层可能会修改或过滤请求头部,导致API Platform无法获取到原始的Authorization头部来传递给BAN请求:
- 在Heroku日志中查看传入的PUT请求的
Authorization头部是否完整,确认Heroku没有篡改这个头部。 - 如果Heroku确实对头部做了处理,可以在API Platform的配置文件
config/packages/api_platform.yaml中显式配置缓存失效客户端的默认头部,直接注入有效的JWT令牌(注意令牌的有效期和刷新机制):
api_platform: cache_invalidation: enabled: true client: 'api_platform.cache_invalidation.client.default' options: headers: Authorization: 'Bearer YOUR_FIXED_JWT_TOKEN'
不过这种方式适合令牌长期有效的场景,如果是用户级别的JWT,还是建议通过事件订阅器动态传递。
4. 添加BAN请求的重试机制
如果首次失败是因为JWT令牌的生效存在延迟(比如Fastly的认证缓存还没同步),可以给BAN请求添加重试逻辑:
- 在自定义的缓存失效订阅器中,捕获BAN请求的401错误,重新获取令牌后再次尝试发送请求。比如使用Guzzle的重试中间件:
// 在订阅器中配置Guzzle客户端的重试中间件 $stack = \GuzzleHttp\HandlerStack::create(); $stack->push(\GuzzleHttp\Middleware::retry(function ($retries, $request, $response) { return $retries < 2 && $response && $response->getStatusCode() === 401; }, function ($retries) { return 100 * pow(2, $retries); })); $client = new \GuzzleHttp\Client(['handler' => $stack]);
内容的提问来源于stack exchange,提问作者Castor Duran
相关产品推荐
相关产品推荐

