跳转到正文

Cookie 存储

默认情况下,Phasis 的 fetch() 会忽略响应中的 Set-Cookie,并且请求不会发送 Cookie 标头。这是服务端嵌入的合适默认设置——通常你不希望某个用户的响应影响另一个用户的请求。

如果确实需要 Cookie——例如运行依赖会话状态的流程、抓取登录后才能访问的内容,或在测试中模拟浏览器行为——可以用 $engine->setCookieJar($jar) 挂载存储。

接口

存储可以是任何实现以下两种方法的对象:

代码
get(url: string): string         // returns a "Cookie:" header value, e.g. "sid=abc; pref=dark"
set(url: string, header: string): void   // receives one Set-Cookie line

PHP 对象和具有匹配成员的 JS 端 JsObject 都可以使用。传入 null 会清空存储。

每个请求跳转时,Phasis 都会调用一次 get(url)(因此重定向到其他主机时会获取新的 Cookie);响应中的每个 Set-Cookie 标头也会调用一次 set(url, line)。Cookie 策略——域名匹配、路径范围、过期时间、SameSite、Secure——由存储负责,而不是由 Phasis 负责。

内存中的存储(测试)

PHP
final class MemoryJar
{
    /** @var array<string, array<string, string>> host → name → "name=value" */
    private array $store = [];

    public function get(string $url): string
    {
        $host = parse_url($url, PHP_URL_HOST) ?? '';
        return implode('; ', $this->store[$host] ?? []);
    }

    public function set(string $url, string $header): void
    {
        $host = parse_url($url, PHP_URL_HOST) ?? '';
        [$nv] = explode(';', $header, 2);
        [$name, $value] = explode('=', trim($nv), 2) + ['', ''];
        if ($name !== '') {
            $this->store[$host][$name] = "$name=$value";
        }
    }
}

$engine->setCookieJar(new MemoryJar());

这足以满足基本的成功路径测试,但没有处理 Domain=、Path=、Expires=,也没有处理 Secure/HttpOnly/SameSite 属性。不要在跨越信任边界的场景中使用它。

Symfony BrowserKit

如果已安装 Symfony 的 HTTP 库,BrowserKit\CookieJar 会妥善处理策略。

PHP
use Symfony\Component\BrowserKit\CookieJar as BrowserCookieJar;
use Symfony\Component\BrowserKit\Cookie;

final class SymfonyJarAdapter
{
    public function __construct(private BrowserCookieJar $jar) {}

    public function get(string $url): string
    {
        $cookies = $this->jar->allValues($url);
        return implode('; ', array_map(
            fn ($n, $v) => "$n=$v",
            array_keys($cookies),
            array_values($cookies),
        ));
    }

    public function set(string $url, string $header): void
    {
        $cookie = Cookie::fromString($header, $url);
        $this->jar->set($cookie);
    }
}

$engine->setCookieJar(new SymfonyJarAdapter(new BrowserCookieJar()));

JS 端存储

如果存储位于 JS 端——例如你想从 JavaScript 检查或填充它——可以通过 JS 对象绑定:

JavaScript
const jar = new Map();
$jar.get = (url) => Array.from(jar.entries()).map(([k, v]) => `${k}=${v}`).join('; ');
$jar.set = (url, header) => {
  const [nv] = header.split(';');
  const [name, value] = nv.split('=');
  jar.set(name.trim(), value.trim());
};
PHP
$engine->eval($script);
$engine->setCookieJar($engine->eval('$jar'));

接收方无论是 PHP 对象还是 JsObject,都会调用相同的 get/set 方法。

每个请求的作用域

存储属于当前运行环境——它会在每个 fetch() 之间持续存在,直到通过 setCookieJar(null) 清除。若要将存储限定在单个请求流程中,请显式处理:

PHP
$engine->setCookieJar($jar);
$engine->eval('await runLoginFlow()');
$engine->setCookieJar(null);

从 PHP 检查

$engine->getCookieJar() 会返回传入的内容——适合在测试中进行断言。

PHP
$jar = new MemoryJar();
$engine->setCookieJar($jar);
$engine->eval('await fetch("https://api.example.com/login", { method: "POST" })');

assertNotEmpty($engine->getCookieJar()->get('https://api.example.com/'));

另请参阅

  • Fetch 传输 — Cookie 存储在 Phasis 的重定向循环内部运行,并叠加在已安装的传输之上。
  • Web API (WPT) — Fetch Pack 中覆盖 Cookie 行为的测试。