核心函數封裝
<?php
/**
* cURL POST 請求(支援 HTTPS 多級驗證)
* @param string $url 目標 URL
* @param array $data POST 欄位
* @param int $timeout 總超時秒數
* @param bool $strictSSL true=嚴格驗證CA,false=寬鬆模式
* @return string|false 響應內容或 false
*/
function curlPost($url, $data = [], $timeout = 30, $strictSSL = true) {
$cacert = __DIR__ . '/cacert.pem'; // CA 根憑證路徑
$isHttps = strncasecmp($url, 'https://', 8) === 0;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => $timeout - 2,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_HTTPHEADER => ['Expect:'], // 避免大數據觸發 100-continue
]);
if ($isHttps) {
if ($strictSSL) {
// === 嚴格模式:僅信任 CA 簽發 ===
curl_setopt_array($ch, [
CURLOPT_SSL_VERIFYPEER => true, // 驗證對端憑證
CURLOPT_CAINFO => $cacert, // 指定 CA 束
CURLOPT_SSL_VERIFYHOST => 2, // 驗證 CN/SAN 匹配域名
]);
} else {
// === 寬鬆模式:信任任何憑證(僅測試/內網) ===
curl_setopt_array($ch, [
CURLOPT_SSL_VERIFYPEER => false, // 不驗證憑證來源
CURLOPT_SSL_VERIFYHOST => 1, // 僅檢查是否有 CN
]);
}
}
$response = curl_exec($ch);
if ($response === false) {
error_log('cURL Error: ' . curl_error($ch) . ' | URL: ' . $url);
}
curl_close($ch);
return $response;
}
?>
三種驗證等級詳解
| 等級 | CURLOPT_SSL_VERIFYPEER | CURLOPT_SSL_VERIFYHOST | 適用場景 |
| 嚴格(生產標準) | true | 2 | 公開 API、支付網關、第三方可信服務 |
| 中等 | true | 1 | 內網自簽憑證、已導入私有 CA |
| 寬鬆(僅加密不驗證) | false | 0/1 | 開發測試、內網無 CA 環境、臨時繞過 |
警告:生產環境嚴禁使用寬鬆模式,將面臨中間人攻擊風險。
CA 憑證檔案 (cacert.pem) 獲取
- 官方來源:https://curl.se/ca/cacert.pem(Mozilla 根憑證庫定期更新)
- 系統內建:
/etc/ssl/certs/ca-certificates.crt (Debian/Ubuntu) 或 /etc/pki/tls/certs/ca-bundle.crt (CentOS/RHEL)
- PHP 內建:PHP 5.6+ 可直接用
openssl_get_cert_locations()['default_cert_file']
$cacert = openssl_get_cert_locations()['default_cert_file'] ?? __DIR__ . '/cacert.pem';
常見錯誤碼與排查
| 錯誤 | 原因 | 解決 |
| SSL certificate problem: self signed certificate | 對端用自簽憑證,CA 不信任 | 導入對端 CA 到 cacert.pem,或寬鬆模式(僅測試) |
| SSL: certificate subject name does not match target host name | 憑證 CN/SAN 不包含請求域名 | 檢查域名拼寫、是否走 CDN/IP 直連、要求對端補全 SAN |
| SSL connect error / handshake failure | TLS 版本不支援(如僅支援 TLS 1.0) | curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2) |
| Operation timed out | 防火牆攔截、目標 IP 被封、MTU 問題 | 檢查網路連通性、抓包分析 |
進階:雙向 TLS (mTLS) / 客戶端憑證
curl_setopt_array($ch, [
CURLOPT_SSLCERT => __DIR__ . '/client.pem', // 客戶端憑證+私鑰
CURLOPT_SSLKEY => __DIR__ . '/client.key', // 若 client.pem 未含私鑰
CURLOPT_KEYPASSWD => 'keypassword', // 私鑰密碼
]);
效能優化提示
- 複用句柄:長生命週期服務中重複使用
$ch,配合 curl_reset()
- 連線池:Swoole/Guzzle 內建連線池,避免重複 TCP/TLS 握手
- HTTP/2:
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0)
- 非阻塞並發:
curl_multi 或 Swoole 協程客戶端
- 上一篇文章: 修改 Apache 配置去除 ThinkPHP URL 中的 index.php 完整步驟
- 下一篇文章: PHP 將文本中的 URL 自動轉換為可點擊鏈接 (保護現有 A/IMG 標簽)
Copyright ©
程式碼之家 All Rights Reserved