对于那些遇到 session file bug的用户,其中cURL
不会在CURLOPT_COOKIEJAR
中写入任何内容(尽管文件存在并且是可写的),使用以下设置:
1.PHP 8.0以上版本
1.使用PHP cURL发出的 * 有状态 * 请求
CURLOPT_COOKIEJAR
和/或CURLOPT_COOKIEFILE
设置为 * 可读/可写 * 位置
1.使用curl_close()
关闭句柄
TL;DR;
不要依赖curl_close()。完成cURL请求后使用unset($curlHandle)
或$curlHandle = null
。
Credits:@volkerschulz
为什么会这样
问题与此有关:PHP's curl_close() documentation
如curl_close()
的文档所述,* 此函数无效。在PHP 8.0.0之前,此函数用于关闭资源。
CURLOPT_COOKIEJAR documentation具有误导性,说明CURLOPT_COOKIEJAR
应该填写 * 当句柄关闭时,保存所有内部cookie的文件名**,例如。在调用curl_close**.* 之后
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);
可复制示例
这是一个重现问题的示例代码。
这样使用:
1.带补丁:/usr/bin/php8.x test.php --with-patch
1.不带补丁:/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
1条答案
按热度按时间erhoui1w1#
从PHP 8.0.0开始,
curl_init()
不再返回 resource,而是返回 CurlHandle 类型的 object。在PHP脚本退出后,垃圾收集器执行操作之前,该对象不会自动销毁。当句柄关闭时,要保存所有内部cookie的文件名,例如。在调用curl_close之后。
当然,最后一部分是误导性的,因为根据其文档,从PHP 8.0.0开始,
curl_close()
不再有效。如果您想强制关闭手柄(即销毁对象),您需要
或放