You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:26:44