PHP cURL 擴充套件(libcurl)是最強大的 HTTP 客戶端工具,支援 HTTP/HTTPS、FTP、SFTP、MQTT 等多種協定,提供精細的連線控制。
<?php
function httpGet(string $url, array $options = []): array {
$ch = curl_init();
$defaultOptions = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true, // 回傳字串而非直接輸出
CURLOPT_FOLLOWLOCATION => true, // 自動追蹤重導向
CURLOPT_MAXREDIRS => 5, // 最大重導向次數
CURLOPT_CONNECTTIMEOUT => 5, // 連線逾時(秒)
CURLOPT_TIMEOUT => 30, // 總執行逾時(秒)
CURLOPT_SSL_VERIFYPEER => true, // 驗證 SSL 憑證(生產環境必開)
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; MyApp/1.0; +https://example.com/bot)',
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Accept-Language: zh-TW,zh;q=0.9,en;q=0.8',
],
CURLOPT_ENCODING => 'gzip, deflate', // 自動解壓縮回應
];
curl_setopt_array($ch, array_merge($defaultOptions, $options));
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($errno) {
throw new RuntimeException("cURL 錯誤 ($errno): $error");
}
// 檢查 HTTP 狀態碼
$httpCode = $info['http_code'] ?? 0;
if ($httpCode < 200 || $httpCode >= 300) {
throw new RuntimeException("HTTP 錯誤 $httpCode: " . substr($response, 0, 200));
}
return [
'body' => $response,
'info' => $info,
'headers' => $info['header_size'] ? substr($response, 0, $info['header_size']) : '',
];
}
// 使用
try {
$result = httpGet('https://api.example.com/users/123');
$data = json_decode($result['body'], true);
print_r($data);
} catch (Exception $e) {
error_log("API 呼叫失敗:" . $e->getMessage());
}
?>
<?php
function httpPost(string $url, array $data, array $options = []): array {
$ch = curl_init();
$isMultipart = false;
foreach ($data as $value) {
if (is_resource($value) || (is_string($value) && strlen($value) > 0 && $value[0] === '@')) {
$isMultipart = true;
break;
}
}
$postFields = $isMultipart ? $data : http_build_query($data);
$defaultOptions = [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 60,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; MyApp/1.0)',
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Content-Type: ' . ($isMultipart ? 'multipart/form-data' : 'application/x-www-form-urlencoded'),
],
];
// 如果是 JSON API
if (isset($options['json']) && $options['json']) {
$defaultOptions[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
$defaultOptions[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json';
$defaultOptions[CURLOPT_HTTPHEADER][] = 'Content-Length: ' . strlen($defaultOptions[CURLOPT_POSTFIELDS]);
}
curl_setopt_array($ch, array_merge($defaultOptions, $options));
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($errno) {
throw new RuntimeException("cURL 錯誤 ($errno): $error");
}
$httpCode = $info['http_code'] ?? 0;
if ($httpCode < 200 || $httpCode >= 300) {
throw new RuntimeException("HTTP 錯誤 $httpCode: " . substr($response, 0, 200));
}
return ['body' => $response, 'info' => $info];
}
// 表單 POST
$result = httpPost('https://api.example.com/login', [
'username' => 'alice',
'password' => 'secret',
]);
// JSON API POST
$result = httpPost('https://api.example.com/orders', [
'items' => [['sku' => 'A001', 'qty' => 2]],
'shipping_address' => 'Taipei...',
], ['json' => true]);
// 檔案上傳(multipart)
$result = httpPost('https://api.example.com/upload', [
'file' => new CURLFile('/path/to/image.jpg', 'image/jpeg', 'photo.jpg'),
'description' => 'My photo',
]);
?>
需同時呼叫多個 API 時,使用 curl_multi 非阻塞並發:
<?php
function multiGet(array $urls, int $maxConcurrent = 10): array {
$mh = curl_multi_init();
$handles = [];
$results = [];
// 建立所有 handle
foreach ($urls as $i => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_USERAGENT => 'MyApp/1.0',
]);
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
$running = null;
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh, 0.1); // 等待活動
} while ($running > 0);
// 收集結果
foreach ($handles as $i => $ch) {
$errno = curl_errno($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
$body = curl_multi_getcontent($ch);
$results[$i] = [
'url' => $urls[$i],
'success' => $errno === 0 && ($info['http_code'] ?? 0) < 300,
'body' => $body,
'info' => $info,
'error' => $errno ? $error : null,
];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $results;
}
// 使用
$urls = [
'https://api.example.com/users/1',
'https://api.example.com/users/2',
'https://api.example.com/users/3',
];
$results = multiGet($urls);
print_r($results);
?>
| 選項 | 說明 | 建議值 |
|---|---|---|
| CURLOPT_TIMEOUT | 總執行逾時(秒) | API: 30, 上傳: 300 |
| CURLOPT_CONNECTTIMEOUT | 連線建立逾時 | 5-10 秒 |
| CURLOPT_FOLLOWLOCATION | 自動追蹤 3xx 重導向 | true |
| CURLOPT_MAXREDIRS | 最大重導向次數 | 5-10 |
| CURLOPT_SSL_VERIFYPEER | 驗證 SSL 憑證 | true (生產環境) |
| CURLOPT_CAINFO | 自訂 CA 憑證束路徑 | /etc/ssl/certs/ca-certificates.crt |
| CURLOPT_PROXY | 代理伺服器 | https://proxy:3128 |
| CURLOPT_HTTPHEADER | 自訂請求標頭 | ['Authorization: Bearer xxx'] |
| CURLOPT_COOKIEJAR/FILE | Cookie 儲存/讀取檔案 | /tmp/cookies.txt |
| CURLOPT_RESUME_FROM | 斷點續傳位移 | 大檔案下載 |
CURLOPT_PINNEDPUBLICKEY 防中間人攻擊。大型專案建議使用成熟的 HTTP Client 套件,提供中介軟、重試、非同步、測試雙工等開箱即用功能:
// Guzzle 範例
$client = new GuzzleHttp\Client([
'base_uri' => 'https://api.example.com/',
'timeout' => 30,
'verify' => true,
]);
$response = $client->post('orders', [
'json' => ['items' => [...]],
'headers' => ['Authorization' => 'Bearer ' . $token],
]);
$data = json_decode($response->getBody(), true);
?>