在 PHP 中發起 HTTP 請求是後端開發的高頻操作:調用第三方 API、抓取遠程數據、模擬瀏覽器行為等。PHP 提供了三種主要方式:cURL、file_get_contents(配合 Stream Context)、原生 Socket(fsockopen)。本文從原理、性能、穩定性、功能性四個維度深度對比,並提供生產級封裝代碼。
| 維度 | cURL | file_get_contents + Stream | Socket (fsockopen) |
|---|---|---|---|
| 底層實現 | libcurl 庫(C 語言) | PHP Streams 層封裝 | 原生 TCP Socket |
| 協議支援 | HTTP/HTTPS/FTP/FTPS/SMTP/POP3/LDAP 等 20+ | HTTP/HTTPS/FTP/FTPS/壓縮流/數據流 | 任意 TCP/UDP 協議(需手寫協議) |
| HTTPS 支援 | 原生完美支援 | 需 OpenSSL 擴展,配置較繁瑣 | 需手動實現 SSL/TLS 握手 |
| POST 請求 | 原生支援,CURLOPT_POSTFIELDS | 需構建 Stream Context | 手動構建 HTTP 報文 |
| Cookie 管理 | CURLOPT_COOKIEJAR/COOKIEFILE 自動管理 | 手動處理 Header | 手動處理 Header |
| 重定向跟隨 | CURLOPT_FOLLOWLOCATION 一鍵開啟 | 需配置 max_redirects | 手動解析 Location Header |
| 超時控制 | 連接/執行/名稱解析分別精確控制 | 單一 timeout 參數,精度有限 | stream_set_timeout 精確控制 |
| DNS 緩存 | 自動緩存,性能優 | 無自動緩存 | 無自動緩存 |
| 斷點續傳 | 支援 Range Header | 支援但配置複雜 | 完全手動實現 |
| 代理支援 | HTTP/SOCKS4/5 完整支援 | HTTP 代理支援 | 手動實現 |
| 證書驗證 | 完整 CA 證書鏈驗證 | 基礎支援 | 需手動實現 |
| 學習曲線 | 中等(參數較多) | 低(類文件操作) | 高(需懂 HTTP 協議細節) |
| 調試難度 | CURLOPT_VERBOSE 輸出詳細信息 | 較難獲取底層信息 | 可見原始報文,調試直觀 |
| 性能 | 極高(C 級實現,連接復用) | 中等 | 較高(但開發成本大) |
| 穩定性 | 企業級,經大規模驗證 | 中等,長連接易出問題 | 依賴開發者實現質量 |
抓取 google.com 首頁(5 次平均): file_get_contents: 2.28 秒 cURL: 0.69 秒 Socket: 0.72 秒 結論:cURL 在網絡 I/O 密集場景下性能優勢明顯,且穩定性遠超 file_get_contents。
<?php
/**
* 通用 HTTP 客戶端(基於 cURL)
*/
class HttpClient {
private $ch;
private $defaultOptions = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP HttpClient)',
CURLOPT_ENCODING => 'gzip,deflate', // 自動解壓
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
];
public function __construct(array $defaultOptions = []) {
$this->defaultOptions = array_merge($this->defaultOptions, $defaultOptions);
}
public function request(string $method, string $url, array $data = [], array $headers = []): array {
$this->ch = curl_init();
$options = $this->defaultOptions;
$options[CURLOPT_URL] = $url;
$options[CURLOPT_CUSTOMREQUEST] = strtoupper($method);
// 處理請求體
if (in_array(strtoupper($method), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart/form-data') !== false) {
// 文件上傳:data 應為 CURLFile 對象數組
$options[CURLOPT_POSTFIELDS] = $data;
} else {
$options[CURLOPT_POSTFIELDS] = is_array($data) ? http_build_query($data) : $data;
}
} elseif ($method === 'GET' && $data) {
$url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($data);
$options[CURLOPT_URL] = $url;
}
// 處理 Header
$headerLines = [];
foreach ($headers as $k => $v) {
$headerLines[] = "$k: $v";
}
if ($headerLines) $options[CURLOPT_HTTPHEADER] = $headerLines;
curl_setopt_array($this->ch, $options);
$response = curl_exec($this->ch);
$errno = curl_errno($this->ch);
$error = curl_error($this->ch);
$info = curl_getinfo($this->ch);
curl_close($this->ch);
return [
'status' => $info['http_code'] ?? 0,
'headers' => $this->parseHeaders($info['header_size'] ? substr($response, 0, $info['header_size']) : ''),
'body' => $info['header_size'] ? substr($response, $info['header_size']) : $response,
'info' => $info,
'error' => $errno ? [$errno, $error] : null,
];
}
// 便捷方法
public function get(string $url, array $params = [], array $headers = []): array {
return $this->request('GET', $url, $params, $headers);
}
public function post(string $url, array $data = [], array $headers = []): array {
return $this->request('POST', $url, $data, $headers);
}
public function postJson(string $url, array $data = [], array $headers = []): array {
$headers['Content-Type'] = 'application/json';
return $this->request('POST', $url, json_encode($data), $headers);
}
private function parseHeaders(string $headerText): array {
$headers = [];
foreach (explode("\r\n", $headerText) as $line) {
if (strpos($line, ':') !== false) {
[$k, $v] = explode(':', $line, 2);
$headers[trim($k)] = trim($v);
}
}
return $headers;
}
}
// 使用示例
$client = new HttpClient();
// GET 請求
$res = $client->get('https://api.example.com/users', ['page' => 1, 'limit' => 20]);
if ($res['status'] === 200) {
$users = json_decode($res['body'], true);
}
// POST JSON API
$res = $client->postJson('https://api.example.com/orders', [
'user_id' => 123,
'items' => [['sku' => 'A001', 'qty' => 2]],
]);
// 文件上傳
$res = $client->post('https://api.example.com/upload', [
'file' => new CURLFile('/path/to/image.jpg', 'image/jpeg', 'avatar.jpg'),
'folder' => 'avatars',
], ['Content-Type' => 'multipart/form-data']);
?>
<?php
/**
* 簡易 HTTP 客戶端(基於 Stream Context)
*/
function httpRequest(string $url, string $method = 'GET', array $data = [], array $headers = [], int $timeout = 30): array {
$contextOptions = [
'http' => [
'method' => strtoupper($method),
'timeout' => $timeout,
'ignore_errors' => true, // 獲取 4xx/5xx 響應體
'header' => '',
],
'ssl' => [
'verify_peer' => true,
'verify_peer_name' => true,
],
];
// 處理 Header
$headerLines = [];
foreach ($headers as $k => $v) {
$headerLines[] = "$k: $v";
}
$contextOptions['http']['header'] = implode("\r\n", $headerLines);
// 處理請求體
if (in_array(strtoupper($method), ['POST', 'PUT', 'PATCH'])) {
$contextOptions['http']['content'] = is_array($data) ? http_build_query($data) : $data;
if (!isset($headers['Content-Type'])) {
$contextOptions['http']['header'] .= "\r\nContent-Type: application/x-www-form-urlencoded";
}
$contextOptions['http']['header'] .= "\r\nContent-Length: " . strlen($contextOptions['http']['content']);
} elseif ($method === 'GET' && $data) {
$url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($data);
}
$context = stream_context_create($contextOptions);
$body = @file_get_contents($url, false, $context);
// 解析響應頭(需 PHP 5.4+)
$headers = [];
if (isset($http_response_header)) {
foreach ($http_response_header as $h) {
if (strpos($h, ':') !== false) {
[$k, $v] = explode(':', $h, 2);
$headers[trim($k)] = trim($v);
}
}
}
// 獲取狀態碼
$status = 0;
if (isset($http_response_header[0]) && preg_match('/HTTP\/\d\.\d\s+(\d+)/', $http_response_header[0], $m)) {
$status = (int)$m[1];
}
return [
'status' => $status,
'headers' => $headers,
'body' => $body ?? '',
'error' => $body === false ? error_get_last() : null,
];
}
// 使用
$res = httpRequest('https://api.example.com/data', 'POST', ['key' => 'value'], ['Authorization' => 'Bearer xxx']);
?>
<?php
/**
* 底層 Socket HTTP 客戶端(教學用途)
*/
function socketHttpRequest(string $host, string $path = '/', string $method = 'GET', array $data = [], array $headers = [], int $port = 80, bool $ssl = false, int $timeout = 30): array {
$target = $ssl ? "ssl://$host" : $host;
$fp = @fsockopen($target, $port, $errno, $errstr, $timeout);
if (!$fp) {
return ['status' => 0, 'error' => [$errno, $errstr], 'body' => ''];
}
stream_set_timeout($fp, $timeout);
// 構建請求
$request = "$method $path HTTP/1.1\r\n";
$request .= "Host: $host\r\n";
$request .= "Connection: Close\r\n";
$request .= "User-Agent: PHP Socket Client\r\n";
foreach ($headers as $k => $v) {
$request .= "$k: $v\r\n";
}
$bodyContent = '';
if (in_array(strtoupper($method), ['POST', 'PUT', 'PATCH']) && $data) {
$bodyContent = is_array($data) ? http_build_query($data) : $data;
$request .= "Content-Type: application/x-www-form-urlencoded\r\n";
$request .= "Content-Length: " . strlen($bodyContent) . "\r\n";
}
$request .= "\r\n" . $bodyContent;
// 發送
fwrite($fp, $request);
// 讀取響應
$response = '';
while (!feof($fp)) {
$response .= fread($fp, 8192);
}
fclose($fp);
// 解析
$headerEnd = strpos($response, "\r\n\r\n");
if ($headerEnd === false) {
return ['status' => 0, 'error' => [0, 'Invalid response'], 'body' => $response];
}
$headerText = substr($response, 0, $headerEnd);
$body = substr($response, $headerEnd + 4);
$headers = [];
$status = 0;
foreach (explode("\r\n", $headerText) as $i => $line) {
if ($i === 0 && preg_match('/HTTP\/\d\.\d\s+(\d+)/', $line, $m)) {
$status = (int)$m[1];
} elseif (strpos($line, ':') !== false) {
[$k, $v] = explode(':', $line, 2);
$headers[trim($k)] = trim($v);
}
}
return ['status' => $status, 'headers' => $headers, 'body' => $body];
}
?>
file_get_contents 可接受,但務必設置 stream_context_create(['http'=>['timeout'=>10]])CURLOPT_SSL_VERIFYPEER => falseCURLOPT_CONNECTTIMEOUT 和 CURLOPT_TIMEOUTcurl_reset())提升高併發性能curl_multi_* 系列函數選對工具,事半功倍。現代 PHP 開發中,cURL 及其封裝庫是 HTTP 客戶端的不二之選。