PHP curl_copy_handle函数

定义和用法

curl_copy_handle() 函数用于复制一个cURL句柄及其所有选项。复制后的句柄是独立的,可以单独设置选项和执行,同时保留了原始句柄的配置。

注意:此函数在PHP 5.5.0及以上版本可用。

语法

curl_copy_handle ( CurlHandle $handle ) : CurlHandle|false

参数

参数 描述
handle curl_init() 返回的 cURL 句柄。

返回值

返回复制后的cURL句柄,失败时返回 false

示例

示例 1:基本用法

// 创建原始cURL句柄
$originalHandle = curl_init();

// 设置一些选项
curl_setopt_array($originalHandle, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_USERAGENT => 'MyCustomAgent/1.0',
    CURLOPT_FOLLOWLOCATION => true
]);

// 复制句柄
$copiedHandle = curl_copy_handle($originalHandle);

// 现在可以分别设置URL并执行
curl_setopt($originalHandle, CURLOPT_URL, 'https://httpbin.org/get');
curl_setopt($copiedHandle, CURLOPT_URL, 'https://httpbin.org/post');

// 设置copiedHandle为POST请求
curl_setopt($copiedHandle, CURLOPT_POST, true);
curl_setopt($copiedHandle, CURLOPT_POSTFIELDS, ['key' => 'value']);

// 执行两个请求
$response1 = curl_exec($originalHandle);
$response2 = curl_exec($copiedHandle);

echo "原始句柄HTTP状态码: " . curl_getinfo($originalHandle, CURLINFO_HTTP_CODE) . "\n";
echo "复制句柄HTTP状态码: " . curl_getinfo($copiedHandle, CURLINFO_HTTP_CODE) . "\n";

// 清理资源
curl_close($originalHandle);
curl_close($copiedHandle);

示例 2:创建请求模板

// 创建一个请求模板
function createRequestTemplate() {
    $template = curl_init();

    // 设置公共选项
    curl_setopt_array($template, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 5,
        CURLOPT_USERAGENT => 'MyApp/1.0',
        CURLOPT_HTTPHEADER => [
            'Accept: application/json',
            'Accept-Language: en-US,en;q=0.9',
            'Cache-Control: no-cache'
        ]
    ]);

    return $template;
}

// 使用模板创建多个请求
$template = createRequestTemplate();

// 复制模板创建不同的请求
$request1 = curl_copy_handle($template);
$request2 = curl_copy_handle($template);
$request3 = curl_copy_handle($template);

// 为每个请求设置不同的URL
curl_setopt($request1, CURLOPT_URL, 'https://api.example.com/users');
curl_setopt($request2, CURLOPT_URL, 'https://api.example.com/products');
curl_setopt($request3, CURLOPT_URL, 'https://api.example.com/orders');

// 可以为某些请求添加特定选项
curl_setopt($request3, CURLOPT_POST, true);
curl_setopt($request3, CURLOPT_POSTFIELDS, json_encode(['status' => 'active']));
curl_setopt($request3, CURLOPT_HTTPHEADER, array_merge(
    ['Content-Type: application/json'],
    curl_getinfo($request3, CURLINFO_HTTPHEADER)
));

// 执行所有请求
$responses = [
    'users' => curl_exec($request1),
    'products' => curl_exec($request2),
    'orders' => curl_exec($request3)
];

// 获取状态码
foreach (['request1' => $request1, 'request2' => $request2, 'request3' => $request3] as $name => $handle) {
    echo "{$name} HTTP状态码: " . curl_getinfo($handle, CURLINFO_HTTP_CODE) . "\n";
}

// 清理资源
curl_close($template);
curl_close($request1);
curl_close($request2);
curl_close($request3);

示例 3:批量处理相似请求

function batchProcessUrls($urls, $commonOptions = []) {
    // 创建基础句柄
    $baseHandle = curl_init();

    // 设置基础选项
    $defaultOptions = [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_USERAGENT => 'BatchProcessor/1.0'
    ];

    curl_setopt_array($baseHandle, array_merge($defaultOptions, $commonOptions));

    $handles = [];
    $multiHandle = curl_multi_init();

    // 为每个URL创建句柄
    foreach ($urls as $index => $url) {
        // 复制基础句柄
        $ch = curl_copy_handle($baseHandle);

        // 设置URL
        curl_setopt($ch, CURLOPT_URL, $url);

        // 添加到多句柄
        curl_multi_add_handle($multiHandle, $ch);

        $handles[$index] = $ch;
    }

    // 执行并发请求
    $active = null;
    do {
        $mrc = curl_multi_exec($multiHandle, $active);
    } while ($active && $mrc == CURLM_OK);

    // 收集结果
    $results = [];
    foreach ($handles as $index => $ch) {
        $results[$index] = [
            'url' => $urls[$index],
            'content' => curl_multi_getcontent($ch),
            'http_code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
            'error' => curl_error($ch)
        ];

        // 清理
        curl_multi_remove_handle($multiHandle, $ch);
        curl_close($ch);
    }

    // 清理基础句柄和多句柄
    curl_close($baseHandle);
    curl_multi_close($multiHandle);

    return $results;
}

// 使用示例
$urls = [
    'https://httpbin.org/get',
    'https://httpbin.org/ip',
    'https://httpbin.org/user-agent',
    'https://httpbin.org/headers'
];

$results = batchProcessUrls($urls);

foreach ($results as $index => $result) {
    echo "请求 {$index} ({$result['url']}): HTTP {$result['http_code']}\n";
    if ($result['error']) {
        echo "  错误: {$result['error']}\n";
    }
}

示例 4:创建可重用的API客户端

class ApiClient {
    private $baseHandle;
    private $baseUrl;

    public function __construct($baseUrl, $defaultHeaders = []) {
        $this->baseUrl = rtrim($baseUrl, '/');
        $this->baseHandle = curl_init();

        // 设置基础配置
        curl_setopt_array($this->baseHandle, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 5
        ]);

        if (!empty($defaultHeaders)) {
            curl_setopt($this->baseHandle, CURLOPT_HTTPHEADER, $defaultHeaders);
        }
    }

    public function request($method, $endpoint, $data = null, $headers = []) {
        // 复制基础句柄
        $ch = curl_copy_handle($this->baseHandle);

        // 构建完整URL
        $url = $this->baseUrl . '/' . ltrim($endpoint, '/');
        curl_setopt($ch, CURLOPT_URL, $url);

        // 设置HTTP方法
        switch (strtoupper($method)) {
            case 'POST':
                curl_setopt($ch, CURLOPT_POST, true);
                break;
            case 'PUT':
            case 'PATCH':
            case 'DELETE':
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));
                break;
            default:
                curl_setopt($ch, CURLOPT_HTTPGET, true);
        }

        // 设置数据
        if ($data !== null) {
            if (is_array($data)) {
                $data = http_build_query($data);
                if (!isset($headers['Content-Type'])) {
                    $headers['Content-Type'] = 'application/x-www-form-urlencoded';
                }
            }

            if (is_string($data)) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
            }
        }

        // 设置自定义头
        if (!empty($headers)) {
            $headerArray = [];
            foreach ($headers as $key => $value) {
                $headerArray[] = "{$key}: {$value}";
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArray);
        }

        // 执行请求
        $response = curl_exec($ch);

        $result = [
            'success' => $response !== false,
            'response' => $response,
            'http_code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
            'error' => curl_error($ch),
            'info' => curl_getinfo($ch)
        ];

        curl_close($ch);
        return $result;
    }

    public function get($endpoint, $headers = []) {
        return $this->request('GET', $endpoint, null, $headers);
    }

    public function post($endpoint, $data, $headers = []) {
        return $this->request('POST', $endpoint, $data, $headers);
    }

    public function put($endpoint, $data, $headers = []) {
        return $this->request('PUT', $endpoint, $data, $headers);
    }

    public function __destruct() {
        if ($this->baseHandle) {
            curl_close($this->baseHandle);
        }
    }
}

// 使用示例
$client = new ApiClient('https://httpbin.org', [
    'Accept' => 'application/json',
    'X-API-Key' => 'your-api-key-here'
]);

// 发送GET请求
$result = $client->get('get');
echo "GET请求: HTTP {$result['http_code']}\n";

// 发送POST请求
$result = $client->post('post', ['name' => 'John', 'age' => 30], [
    'Content-Type' => 'application/json'
]);
echo "POST请求: HTTP {$result['http_code']}\n";

// 发送PUT请求
$result = $client->put('put', json_encode(['status' => 'updated']), [
    'Content-Type' => 'application/json'
]);
echo "PUT请求: HTTP {$result['http_code']}\n";

示例 5:性能对比 - 复制 vs 新建

// 测试复制句柄的性能
function testCopyHandle($iterations) {
    // 创建基础句柄
    $baseHandle = curl_init();
    curl_setopt_array($baseHandle, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_USERAGENT => 'TestAgent',
        CURLOPT_HTTPHEADER => ['Accept: application/json']
    ]);

    $startTime = microtime(true);
    $memoryStart = memory_get_usage();

    $handles = [];
    for ($i = 0; $i < $iterations; $i++) {
        $handles[] = curl_copy_handle($baseHandle);
    }

    $endTime = microtime(true);
    $memoryEnd = memory_get_usage();

    // 清理
    foreach ($handles as $handle) {
        curl_close($handle);
    }
    curl_close($baseHandle);

    return [
        'time' => $endTime - $startTime,
        'memory' => $memoryEnd - $memoryStart
    ];
}

// 测试新建句柄的性能
function testNewHandle($iterations) {
    $startTime = microtime(true);
    $memoryStart = memory_get_usage();

    $handles = [];
    for ($i = 0; $i < $iterations; $i++) {
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_USERAGENT => 'TestAgent',
            CURLOPT_HTTPHEADER => ['Accept: application/json']
        ]);
        $handles[] = $ch;
    }

    $endTime = microtime(true);
    $memoryEnd = memory_get_usage();

    // 清理
    foreach ($handles as $handle) {
        curl_close($handle);
    }

    return [
        'time' => $endTime - $startTime,
        'memory' => $memoryEnd - $memoryStart
    ];
}

// 运行测试
$iterations = 100;
$copyResult = testCopyHandle($iterations);
$newResult = testNewHandle($iterations);

echo "性能对比测试 ({$iterations} 次迭代):\n";
echo "================================\n";
echo "复制句柄:\n";
echo "  时间: " . round($copyResult['time'] * 1000, 2) . " 毫秒\n";
echo "  内存: " . round($copyResult['memory'] / 1024, 2) . " KB\n";
echo "\n新建句柄:\n";
echo "  时间: " . round($newResult['time'] * 1000, 2) . " 毫秒\n";
echo "  内存: " . round($newResult['memory'] / 1024, 2) . " KB\n";
echo "\n性能提升:\n";
echo "  时间节省: " . round((1 - $copyResult['time'] / $newResult['time']) * 100, 2) . "%\n";
echo "  内存节省: " . round((1 - $copyResult['memory'] / $newResult['memory']) * 100, 2) . "%\n";

curl_copy_handle vs curl_reset

方面 curl_copy_handle curl_reset
作用 创建新的独立句柄副本 重置现有句柄的所有选项
返回值 新的cURL句柄 void
内存使用 创建新资源,使用更多内存 重用现有资源,内存效率高
使用场景 需要多个相似但独立的连接 复用同一个句柄进行不同请求
并发支持 适合并发请求 适合顺序请求

注意事项

资源管理:复制后的句柄是独立资源,需要单独调用 curl_close() 来释放。不要忘记清理所有创建的句柄。
使用场景:
  • 创建多个相似配置的并发请求时
  • 基于模板创建多个API请求时
  • 需要保持原始句柄配置不变,同时创建变体时
  • 在循环中创建多个相似请求时
最佳实践:
  • 为复杂的配置创建模板句柄
  • 在并发请求中使用复制句柄
  • 合理管理复制的句柄资源
  • 考虑使用对象封装来管理句柄生命周期

常见问题

Q: curl_copy_handle() 会复制所有选项吗?

A: 是的,curl_copy_handle() 会复制源句柄的所有选项设置,包括URL、HTTP方法、头信息、超时设置等。

Q: 复制后的句柄和原始句柄有什么关系?

A: 复制后的句柄是独立的资源,对复制句柄的修改不会影响原始句柄,反之亦然。它们可以独立使用和配置。

Q: 什么时候应该使用curl_copy_handle()而不是创建新句柄?

A: 当需要创建多个配置相似的cURL句柄时,使用 curl_copy_handle() 可以提高性能并简化代码。特别是当基础配置复杂时,复制可以避免重复设置选项。

Q: 复制句柄会影响性能吗?

A: curl_copy_handle() 通常比创建新句柄并设置所有选项要快,特别是当配置复杂时。但在只需要少量简单请求时,直接创建新句柄可能更简单。

相关函数