首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP cURL 取得回應內容完整指南:CURLOPT_RETURNTRANSFER、curl_multi_getcontent、錯誤處理

     編輯:PHP 相關知識

cURL 取得回應內容是 HTTP 客戶端核心操作。本文詳細解析 CURLOPT_RETURNTRANSFER、curl_multi_getcontent()、同步/非同步模式、串流處理大檔案、編碼轉換、錯誤處理,提供生產級封裝類別。

一、核心參數:CURLOPT_RETURNTRANSFER

<?php
$ch = curl_init('https://api.example.com/data');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 關鍵:回傳字串而非直接輸出
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch); // 現在 $response 是字串

if ($response === false) {
    throw new RuntimeException("cURL 執行失敗: " . curl_error($ch));
}

$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

// 處理回應
$data = json_decode($response, true);

預設值為 false:直接輸出到 STDOUT,無法變數接收。**必須設為 true**。

二、非同步多請求:curl_multi_getcontent()

<?php
function fetchMulti(array $urls, int $concurrency = 10): array {
    $mh = curl_multi_init();
    $handles = [];
    $results = [];

    foreach ($urls as $url) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 10,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_PRIVATE => $url,
        ]);
        curl_multi_add_handle($mh, $ch);
        $handles[] = $ch;
    }

    $running = null;
    do {
        curl_multi_exec($mh, $running);
        curl_multi_select($mh, 0.1);
        while ($info = curl_multi_info_read($mh)) {
            $ch = $info['handle'];
            $url = curl_getinfo($ch, CURLINFO_PRIVATE);
            $content = curl_multi_getcontent($ch); // 關鍵:取得內容
            $errno = curl_errno($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

            $results[$url] = [
                'content' => $errno ? null : $content,
                'http_code' => $httpCode,
                'error' => $errno ? curl_error($ch) : null,
            ];

            curl_multi_remove_handle($mh, $ch);
            curl_close($ch);
        }
    } while ($running);

    curl_multi_close($mh);
    return $results;
}

三、大檔案串流處理 (避免記憶體爆炸)

<?php
function downloadToFile(string $url, string $savePath, int $timeout = 300): bool {
    $fp = fopen($savePath, 'wb');
    if (!$fp) throw new RuntimeException("無法開啟寫入檔案: $savePath");

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_FILE => $fp,              // 直接寫入檔案指標
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_CONNECTTIMEOUT => 30,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_NOPROGRESS => false,      // 可啟用進度回調
        CURLOPT_PROGRESSFUNCTION => function($ch, $dlTotal, $dlNow, $ulTotal, $ulNow) {
            if ($dlTotal > 0) {
                $pct = round($dlNow / $dlTotal * 100, 1);
                echo "\r下載進度: {$pct}% ({$dlNow}/{$dlTotal} bytes)";
            }
        },
    ]);

    $result = curl_exec($ch);
    $errno = curl_errno($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    fclose($fp);
    curl_close($ch);

    if ($errno || $httpCode < 200 || $httpCode >= 300) {
        @unlink($savePath); // 失敗刪除不完整檔案
        throw new RuntimeException("下載失敗: HTTP $httpCode, cURL $errno: " . curl_error($ch));
    }
    return true;
}

四、完整封裝類別:HttpClient

<?php
namespace App\Http;

class HttpClient {
    private array $defaultOptions;
    private ?resource $multiHandle = null;

    public function __construct(array $defaultOptions = []) {
        $this->defaultOptions = array_merge([
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_USERAGENT => 'App/HttpClient/1.0',
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_ENCODING => 'gzip, deflate', // 自動解壓
        ], $defaultOptions);
    }

    public function get(string $url, array $options = []): Response {
        return $this->request('GET', $url, null, $options);
    }

    public function post(string $url, mixed $data = null, array $options = []): Response {
        return $this->request('POST', $url, $data, $options);
    }

    public function request(string $method, string $url, mixed $data = null, array $options = []): Response {
        $ch = curl_init($url);
        $opts = array_merge($this->defaultOptions, $options);
        $opts[CURLOPT_CUSTOMREQUEST] = $method;

        if ($data !== null) {
            if (is_array($data) && !$this->isAssoc($data)) {
                $opts[CURLOPT_POSTFIELDS] = http_build_query($data);
            } elseif (is_array($data) || is_object($data)) {
                $opts[CURLOPT_POSTFIELDS] = json_encode($data);
                $opts[CURLOPT_HTTPHEADER] = array_merge(
                    $opts[CURLOPT_HTTPHEADER] ?? [],
                    ['Content-Type: application/json']
                );
            } else {
                $opts[CURLOPT_POSTFIELDS] = $data;
            }
        }

        curl_setopt_array($ch, $opts);

        $content = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = $errno ? curl_error($ch) : null;
        $info = curl_getinfo($ch);
        curl_close($ch);

        if ($errno) {
            throw new HttpException("cURL 錯誤 ($errno): $error", 0, null, $info);
        }

        $httpCode = $info['http_code'] ?? 0;
        $headers = $this->parseHeaders($info['header_size'] ? substr($content, 0, $info['header_size']) : '');
        $body = $info['header_size'] ? substr($content, $info['header_size']) : $content;

        return new Response($httpCode, $headers, $body, $info);
    }

    public function multiGet(array $urls, int $concurrency = 10): Generator {
        // ... 同前方 fetchMulti 實作,yield Response 物件
    }

    private function parseHeaders(string $raw): array {
        $headers = [];
        foreach (explode("\r\n", trim($raw)) as $line) {
            if (strpos($line, ':') !== false) {
                [$k, $v] = explode(':', $line, 2);
                $headers[strtolower(trim($k))] = trim($v);
            }
        }
        return $headers;
    }

    private function isAssoc(array $arr): bool {
        return array_keys($arr) !== range(0, count($arr) - 1);
    }
}

class Response {
    public function __construct(
        public int $httpCode,
        public array $headers,
        public string $body,
        public array $info
    ) {}

    public function json(bool $assoc = true): mixed {
        return json_decode($this->body, $assoc);
    }

    public function xml(): SimpleXMLElement {
        return simplexml_load_string($this->body);
    }

    public function isSuccess(): bool {
        return $this->httpCode >= 200 && $this->httpCode < 300;
    }

    public function __toString(): string {
        return $this->body;
    }
}

class HttpException extends RuntimeException {
    public function __construct(string $message, int $code = 0, ?Throwable $previous = null, public array $info = []) {
        parent::__construct($message, $code, $previous);
    }
}

五、編碼與 Content-Type 處理

情況處理
回應為 JSONjson_decode($body, true),檢查 json_last_error()
回應為 XMLsimplexml_load_string($body) 或 DOMDocument
編碼非 UTF-8從 Content-Type: charset=xxx 解析,mb_convert_encoding($body, 'UTF-8', $from)
gzip/deflate 壓縮設定 CURLOPT_ENCODING => 'gzip, deflate',cURL 自動解壓
二進位檔案直接使用 $body 或 CURLOPT_FILE 寫檔

六、常見錯誤與除錯

// 除錯模式:輸出完整請求/回應
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_VERBOSE, true);
$verbose = fopen('php://temp', 'rw+');
curl_setopt($ch, CURLOPT_STDERR, $verbose);
curl_exec($ch);
rewind($verbose);
echo "=== cURL Verbose ===\n" . stream_get_contents($verbose);
fclose($verbose);

// 檢查 SSL 憑證鏈
curl_setopt($ch, CURLOPT_CERTINFO, true);
$info = curl_getinfo($ch);
print_r($info['certinfo'] ?? []);

七、總結

  1. CURLOPT_RETURNTRANSFER = true 是取得回應字串的必要條件
  2. curl_multi_getcontent() 用於非同步多請求取得內容
  3. 大檔案用 CURLOPT_FILE 串流寫入,避免記憶體耗盡
  4. 封裝 HttpClient 類別 統一錯誤處理、編碼、Header 解析、重試邏輯
  5. 啟用 CURLOPT_ENCODING 自動處理壓縮回應
  6. 生產環境必須 SSL 驗證,設定 openssl.cafile
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved