从PHP响应中删除重复的“Set-Cookie”头部

6

这是一个来自大型应用程序的示例脚本,但是它展示了我正在尝试完成的一般过程。如果我有以下脚本:

<?php
ob_start();

setcookie('test1', 'first');
setcookie('test1', 'second');
setcookie('test1', 'third');
setcookie('test2', 'keep');

//TODO remove duplicate test1 from headers

ob_end_clean();
die('end test');

我收到了以下的响应(通过 Fiddler 查看):
HTTP/1.1 200 OK
Date: Tue, 25 Apr 2017 21:54:45 GMT
Server: Apache/2.4.17 (Win32) OpenSSL/1.0.2d PHP/5.5.30
X-Powered-By: PHP/5.5.30
Set-Cookie: test1=first
Set-Cookie: test1=second
Set-Cookie: test1=third
Set-Cookie: test2=keep
Content-Length: 8
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

end test

问题在于Set-Cookie: test1存在3次,因此不必要地增加了标头大小。 (再次说明,这是一个简化的示例 - 实际上,我正在处理约10个重复的cookie,范围为~800字节。) 有什么可以替代TODO的内容,可以完全删除标头或仅显示一次吗? 即我的最终目标如下:
HTTP/1.1 200 OK
Date: Tue, 25 Apr 2017 21:54:45 GMT
Server: Apache/2.4.17 (Win32) OpenSSL/1.0.2d PHP/5.5.30
X-Powered-By: PHP/5.5.30
Set-Cookie: test1=third
Set-Cookie: test2=keep
Content-Length: 8
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

end test

尽管Set-Cookie: test1=third可能不存在,这也没关系,但需要保留Set-Cookie: test2=keep。当我尝试使用setcookie('test1', '', 1);删除cookie时,它会添加一个额外的标头来将其标记为过期:
Set-Cookie: test1=first
Set-Cookie: test1=second
Set-Cookie: test1=third
Set-Cookie: test2=keep
Set-Cookie: test1=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0

如果我试着去掉头部:

if (!headers_sent()) {
    foreach (headers_list() as $header) {
        if (stripos($header, 'Set-Cookie: test1') !== false) {
            header_remove('Set-Cookie');
        }
    }
}

它移除了所有的Set-Cookie头信息,而我只需要移除test1

你能把头信息存储在一个数组中吗?例如,如果你有一个View类,它可以有一个(静态?)方法_SetCookie()_,该方法将键和值存储在一个关联数组中。然后,在发送最终的头信息时,只需遍历该关联数组并调用_setcookie()_,这样每个键只会被调用一次... - Sᴀᴍ Onᴇᴌᴀ
我希望能这样设置!理想情况下是这样的,但是当它到达我的脚本时,Set-Cookie: test1=... 已经运行了多次,所以我可以注入任何代码的地方就在 TODO - WOUNDEDStevenJones
2个回答

5
如您在上一个代码块中建议的那样,可以使用headers_list()函数来检查已发送的标头。使用它,每个cookie的最后一个值都可以存储在关联数组中。可以使用explode()(以及trim())来提取名称和值。
当检测到具有相同名称的多个cookie时,我们可以像您所做的那样使用header_remove()调用,然后将cookie设置为最终值。请参见下面的示例,以及这个PHP Fiddle示例
if (!headers_sent()) {
    $cookiesSet = []; //associative array to store the last value for each cookie
    $rectifyCookies = false; //multiple values detected for same cookie name

    foreach (headers_list() as $header) {
        if (stripos($header, 'Set-Cookie:') !== false) {
            list($setCookie, $cookieValue) = explode(':', $header);
            list($cookieName, $cookieValue) = explode('=', trim($cookieValue));
            if (array_key_exists($cookieName, $cookiesSet)) {
                $rectifyCookies = true;
            }
            $cookiesSet[$cookieName] = $cookieValue;
        }
    }
    if ($rectifyCookies) {
        header_remove('Set-Cookie');
        foreach($cookiesSet as $cookieName => $cookieValue) {
            //might need to consider optional 3rd - 8th parameters 
            setcookie($cookieName, $cookieValue);
        }
    }
}

输出:

Cache-Control 缓存控制:最大缓存时间为0,不允许缓存、不存储,必须重新验证
Connection 连接方式:保持连接
Content-Encoding 内容编码:gzip
Content-Type 内容类型:文本/HTML; 字符集=utf-8
Date 日期:2017年4月26日15:31:33 GMT
Expires 到期时间:1984年1月11日05:00:00 GMT
Pragma 缓存模式:没有缓存
Server 服务器:nginx
Set-Cookie 设置Cookie:test1=third
                     test2=keep
Transfer-Encoding 数据传输编码:分块传输
Vary 变化因素:接受编码方式


太棒了,这正是我在寻找的! - WOUNDEDStevenJones
这很棒。我建议更改两个地方以正确处理其他cookie头参数。1)将两个explode更改为限制为2,例如:explode(':', $header, 2) 2)使用header()函数而不是setcookie(),如header("Set-Cookie: {$cookieName}={$cookieValue}", false); 这些更改将保留原始Set-Cookie标头中的任何过期、Max-Age等参数。 - Stenerson
@Stenerson 好的,我同意在调用explode()时添加第三个参数,但是在阅读了这篇答案之后,我没有看到不使用setcookie()的理由(除非32位2038溢出问题影响您)... - Sᴀᴍ Onᴇᴌᴀ
注意,这段代码可以放入一个函数中,并通过header-register_callback()进行注册。 - miken32

0
我不明白为什么你认为你展示的删除Cookie代码会删除test2的setcookie。
如果你的代码多次设置相同的Cookie,则需要更改代码,使其停止多次设置Cookie!其他任何方法都是懒惰的权宜之计。

你的意思是 header_remove('Set-Cookie'); 吗?根据它的定义(http://php.net/manual/en/function.header-remove.php),它会移除所有的 Set-Cookie 头信息。所以无论 cookie 的名称是什么,它都会清除所有的头信息。我正在使用一个框架,似乎它会多次添加 cookie,所以我只是想在响应返回之前清理一下,而不是修改核心代码。 - WOUNDEDStevenJones
1
当然!撇开这种错误解决问题的方式不谈,你可以将setcookie头解析为一个名称=>值的数组,然后使用header_remove('Set-Cookie'),再次遍历数组。但这会导致一个稍微好一点的解决方案,即在第一次填充数组时不调用setcookie(),直到数据被编组(但仍然是错误的解决方案)。 - symcbean

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接