方案對比總覽
| 方案 | 依賴 | 特性 | 適用場景 |
| Stream Context | 無擴展 | 原生、輕量、支援 HTTPS | 簡單 GET/POST、無 cURL 環境 |
| Socket (fsockopen) | 無擴展 | 底層控制、支援持久連接、自定義協議 | 特殊協議、長連接、高性能微服務調用 |
| cURL | 需 php_curl.dll | 功能最全、多協議、並發、代理、證書、限速 | 生產環境首選、複雜 HTTP 交互 |
方案一:Stream Context(原生 file_get_contents)
<?php
/**
* 發送 POST 請求(Stream 版)
* @param string $url 請求地址
* @param array $postData 鍵值對數據
* @param int $timeout 超時秒數
* @return string|false 響應體或失敗
*/
function sendPostStream($url, array $postData, $timeout = 15) {
$postData = http_build_query($postData);
$options = [
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: " . strlen($postData),
'content' => $postData,
'timeout' => $timeout,
'ignore_errors' => true, // 獲取 4xx/5xx 響應體
],
'ssl' => [
'verify_peer' => false, // 生產環境建議 true 並配置 CA
'verify_peer_name' => false,
]
];
$context = stream_context_create($options);
$result = @file_get_contents($url, false, $context);
return $result;
}
// 使用示例
$response = sendPostStream('https://api.example.com/login', [
'username' => 'admin',
'password' => 'secret'
]);
?>
方案二:Socket 底層實現(教學/特殊需求)
<?php
/**
* Socket 發送原始 HTTP 請求
* @param string $host 主機名
* @param string $path 路徑
* @param string $postString 已編碼的 POST 數據字符串
* @param int $port 端口
* @param int $timeout 超時
* @return string 響應全文(含頭部)
*/
function requestBySocket($host, $path, $postString, $port = 80, $timeout = 30) {
$fp = @fsockopen($host, $port, $errno, $errstr, $timeout);
if (!$fp) throw new Exception("$errstr ($errno)");
$request = "POST $path HTTP/1.1\r\n";
$request .= "Host: $host\r\n";
$request .= "User-Agent: PHP-Socket-Client\r\n";
$request .= "Content-Type: application/x-www-form-urlencoded\r\n";
$request .= "Content-Length: " . strlen($postString) . "\r\n";
$request .= "Connection: Close\r\n\r\n";
$request .= $postString;
fwrite($fp, $request);
$response = '';
while (!feof($fp)) {
$response .= fgets($fp, 4096);
}
fclose($fp);
return $response;
}
// 使用示例
$postString = http_build_query(['app' => 'socket', 'version' => 'beta']);
$rawResponse = requestBySocket('api.example.com', '/endpoint', $postString);
// 需手動分離頭部與體:list($header, $body) = explode("\r\n\r\n", $rawResponse, 2);
?>
方案三:cURL 完整封裝(生產環境標準)
<?php
/**
* cURL 發送 POST 請求(通用封裝)
* @param string $url
* @param array|string $postData
* @param array $options 額外 curl_setopt 選項
* @return array ['code'=>http_code, 'body'=>string, 'header'=>string, 'info'=>array]
*/
function sendPostCurl($url, $postData, array $options = []) {
$ch = curl_init();
$defaultOptions = [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => is_array($postData) ? http_build_query($postData) : $postData,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true, // 返回頭部用於解析狀態碼
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false, // 生產環境請設 true 並配置 CURLOPT_CAINFO
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_USERAGENT => 'PHP-cURL-Client/1.0',
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
];
curl_setopt_array($ch, array_merge($defaultOptions, $options));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$errno = curl_errno($ch);
$error = curl_error($ch);
curl_close($ch);
if ($errno) throw new Exception("cURL Error ($errno): $error");
// 分離頭部與體
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
return [
'code' => $httpCode,
'body' => $body,
'header' => $header,
'info' => curl_getinfo($ch)
];
}
// 使用示例
try {
$res = sendPostCurl('https://api.example.com/order', [
'order_id' => '20240101001',
'amount' => 99.9
]);
echo "HTTP $res[code]\n";
echo $res['body'];
} catch (Exception $e) {
echo "請求失敗: " . $e->getMessage();
}
?>
進階:統一請求類(策略模式)
<?php
class HttpClient {
public static function post($url, $data = [], $timeout = 5) {
// 優先 cURL,降級 Stream
if (function_exists('curl_init')) {
return self::postCurl($url, $data, $timeout);
}
return self::postStream($url, $data, $timeout);
}
// ... 內部實現 postCurl / postStream / postSocket
}
?>
關鍵最佳實踐
- 超時控制:務必設置連接超時(
CURLOPT_CONNECTTIMEOUT)與總超時(CURLOPT_TIMEOUT),防止阻塞進程。
- SSL 驗證:生產環境必須開啟
CURLOPT_SSL_VERIFYPEER 並配置 CA 憑證(CURLOPT_CAINFO)。
- 錯誤處理:檢查 HTTP 狀態碼(
2xx 成功)、cURL 錯誤碼、業務錯誤碼三層。
- 並發請求:大批量調用使用
curl_multi_* 或 Guzzle 異步客戶端。
- 數據格式:API 多為 JSON,發送時設置
Content-Type: application/json 並 json_encode($data)。