提问人:Maurizio 提问时间:10/10/2023 最后编辑:Maurizio 更新时间:10/10/2023 访问量:120
curl_close() 从 PHP 8 开始不写入CURLOPT_COOKIEJAR会话文件 [已关闭]
curl_close() not writing CURLOPT_COOKIEJAR session file from PHP 8 onwards [closed]
问:
对于那些遇到会话文件错误的人,其中没有在里面写任何东西(尽管文件存在并且是可写的),使用以下设置:cURL
CURLOPT_COOKIEJAR
- PHP 8.0 以上
- 使用 PHP cURL 发出有状态请求
CURLOPT_COOKIEJAR
和/或设置为可读/可写位置CURLOPT_COOKIEFILE
- 用于关闭手柄
curl_close()
TL;博士;
不要依赖 curl_close()。使用或完成 cURL 请求后。unset($curlHandle)
$curlHandle = null
为什么会这样
问题与此有关:PHP 的 curl_close() 文档
如文档所述,此函数不起作用。在 PHP 8.0.0 之前,此函数用于关闭资源。curl_close()
具有误导性的是,CURLOPT_COOKIEJAR文档中指出,当句柄关闭时,例如在调用curl_close之后,应填写要保存所有内部 cookie 的文件的名称。CURLOPT_COOKIEJAR
PHP 5.6、7.x 和 8.x 之间的交叉兼容性
为了修补必须保证在多个 PHP 版本上运行的代码,只需遵循以下模式:
$ch = curl_init($url);
...
...
$response = curl_exec($ch);
curl_close($ch);
// always unset curl handle after having closed it.
unset($ch);
可重现的例子
这是重现问题的示例代码。
像这样使用它:
- 带补丁 :
/usr/bin/php8.x test.php --with-patch
- 没有补丁:
/usr/bin/php8.x test.php
<?php
// exit if not called from CLI
if (php_sapi_name() !== 'cli') {
die('This script can only be called from CLI');
}
// exit if the major version of PHP is not 8 or greater
if (substr(phpversion(), 0, 1) < 8) {
die('This script requires PHP 8 or greater');
}
// exit if we don't have cURL support
if (! function_exists('curl_init')) {
die('This script requires cURL support');
}
// get the command line option "--with-patch" (if any)
$withPatch = in_array('--with-patch', $argv);
$withPatchClaim = $withPatch ? 'with the unset($ch) patch' : 'without the unset($ch) patch';
// get the major+minor version of PHP
$phpVersion = substr(phpversion(), 0, 3);
// get the version of the cUrl module (if available)
$curlVersion = curl_version()['version'];
echo "Testing with PHP {$phpVersion}, cURL {$curlVersion}. Running {$withPatchClaim}\n";
// define our base path
$basepath = dirname(__FILE__);
// setup a randomic session file for cURL in the current directory, for debugging purposes
$rand = md5(uniqid(rand(), true));
$sessionFile = "{$basepath}/{$rand}.txt";
file_put_contents($sessionFile, '');
// init a curl request to https://dba.stackexchange.com/questions/320782/any-benefit-in-replacing-a-varchar-for-an-index-in-the-main-table
// this url is intended to save cookies in the session file
$url = 'https://dba.stackexchange.com/questions/320782/any-benefit-in-replacing-a-varchar-for-an-index-in-the-main-table';
$ch = curl_init($url);
if (! $ch) {
die('Could not initialize a cURL session');
}
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding: utf-8',
'Accept-Charset: utf-8;q=0.7,*;q=0.7',
'Cache-Control: no-cache',
'Connection: Keep-Alive',
'Referer: https://www.google.com/',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0',
]);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// see https://curl.se/libcurl/c/CURLOPT_COOKIELIST.html
curl_setopt($ch, CURLOPT_COOKIELIST, 'FLUSH');
curl_setopt($ch, CURLOPT_COOKIEJAR, $sessionFile);
// this would not work even with CURLOPT_COOKIEFILE pointing to the same file
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
// execute the request
$response = curl_exec($ch);
$error = curl_error($ch);
$effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$primaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
// as of PHP 8 onwards, the curl_close() call has no effect: https://www.php.net/manual/en/function.curl-close.php
curl_close($ch);
// if we've called this script with "--with-patch", apply the @volkerschulz patch https://stackoverflow.com/a/77261355/1916292
if ($withPatch) {
unset($ch);
// the garbage collection is not needed.
// it is intentionally left here to let the reader understand that the issue is related to destroying the $ch variable
gc_collect_cycles();
}
// make the response a one-liner
$response = preg_replace('/\s\s+/', ' ', str_replace([chr(13).chr(10), chr(10), chr(9)], ' ', $response));
// print the response (cap to a max of 128 chars)
echo "GET {$effectiveUrl} ({$httpStatusCode})\n";
echo "Primary IP: {$primaryIp}\n";
echo "Error: {$error}\n";
echo 'Response: '.substr($response, 0, 128)." ...\n";
// debug the contents of the session file
echo str_repeat('-', 80)."\n";
echo "Session file: {$sessionFile}\n";
// show the difference from $withPatch and without it
if (! $withPatch) {
echo "Session file contents without the unset(\$ch) patch (BUGGED):\n";
echo 'Session Filesize: '.filesize($sessionFile)."\n";
echo file_get_contents($sessionFile)."\n";
} else {
echo "Session file contents WITH the unset(\$ch) patch (WORKING):\n";
echo 'Session Filesize: '.filesize($sessionFile)."\n";
echo file_get_contents($sessionFile)."\n";
}
启用修补程序的脚本输出:
maurizio:~/test (master ?) $ php8.2 test.php
Testing with PHP 8.2, cURL 7.81.0. Running without the unset($ch) patch
GET https://dba.stackexchange.com/questions/320782/any-benefit-in-replacing-a-varchar-for-an-index-in-the-main-table (200)
Primary IP: 104.18.10.86
Error:
Response: HTTP/2 200 date .... (OMISSIS)
----------------------------------------------------------------------
Session file: /home/maurizio/test/100881e90c76fd3a63ee2e6b3b710fea.txt
Session file contents without the unset($ch) patch (BUGGED):
Session Filesize: 0
启用修补程序的脚本输出:
maurizio:~/test (master ?) $ php8.2 test.php --with-patch
Testing with PHP 8.2, cURL 7.81.0. Running with the unset($ch) patch
GET https://dba.stackexchange.com/questions/320782/any-benefit-in-replacing-a-varchar-for-an-index-in-the-main-table (200)
Primary IP: 104.18.10.86
Error:
Response: HTTP/2 200 date .... (OMISSIS)
----------------------------------------------------------------------
Session file: /home/maurizio/test/aa1524b52055bad838f18fae44e83b22.txt
Session file contents WITH the unset($ch) patch (WORKING):
Session Filesize: 431
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_.stackexchange.com TRUE / TRUE 1696928967 __cf_bm OMISSIS
#HttpOnly_.stackexchange.com TRUE / TRUE 1728549567 prov OMISSIS
答:
3赞
volkerschulz
10/10/2023
#1
从 PHP 8.0.0 开始,不再返回资源,而是返回 CurlHandle 类型的对象。在 PHP 脚本退出后,在垃圾回收器开始操作之前,此对象不会被自动销毁。curl_init()
CURLOPT_COOKIEJAR文档指出:
在句柄关闭时(例如,在调用 curl_close 之后)保存所有内部 cookie 的文件的名称。
当然,最后一部分是误导性的,因为根据其文档,从 PHP 8.0.0 开始不再起作用。curl_close()
如果要强制关闭手柄(即销毁对象),则需要
unset($ch);
或把
$ch = null;
评论
1赞
Maurizio
10/10/2023
谢谢你的贡献。CURLOPT_COOKIEJAR 的文档确实具有误导性,人们可以很容易地将问题与其他问题联系起来,而不是理解从 PHP 8.0 开始的 curl_close() 差异。我会更新我的答案,给出正确的说明和有意义的标题。
0赞
volkerschulz
10/10/2023
有人可能会争辩说,提及只是一个“给出的例子”,仍然适用于较旧的PHP版本,但我100%同意应该在文档中进行澄清。curl_close
评论
unset($ch);
curl_close()