後端服務間互動、爬蟲、API 串接常需模擬瀏覽器 POST 表單。本文詳細對比 fsockopen (原生 Socket) 與 cURL 兩種方案,涵蓋完整程式碼、錯誤處理、效能差異、適用場景。
function postWithFsockopen(string $url, array $data, int $timeout = 30): string|false {
$parsed = parse_url($url);
if (!$parsed) return false;
$host = $parsed['host'];
$port = $parsed['port'] ?? ($parsed['scheme'] === 'https' ? 443 : 80);
$path = ($parsed['path'] ?? '/') . ($parsed['query'] ? '?' . $parsed['query'] : '');
// 建構 POST 體
$body = http_build_query($data);
$headers = [
"POST {$path} HTTP/1.1",
"Host: {$host}",
"Content-Type: application/x-www-form-urlencoded",
"Content-Length: " . strlen($body),
"Connection: close",
"User-Agent: PHP/" . PHP_VERSION,
"", // 空行分隔 Header/Body
$body
];
$request = implode("\r\n", $headers);
// SSL/TLS 支援
$scheme = $parsed['scheme'] === 'https' ? 'ssl://' : '';
$fp = @fsockopen($scheme . $host, $port, $errno, $errstr, $timeout);
if (!$fp) {
error_log("fsockopen 失敗: $errstr ($errno)");
return false;
}
stream_set_timeout($fp, $timeout);
fwrite($fp, $request);
$response = '';
while (!feof($fp)) {
$response .= fgets($fp, 4096);
}
fclose($fp);
// 簡易分離 Header/Body
list($header, $body) = explode("\r\n\r\n", $response, 2) + ['', ''];
// 檢查狀態碼
if (!preg_match('/HTTP\/\d\.\d\s+2\d\d/', $header)) {
error_log("HTTP 錯誤回應: " . substr($header, 0, 200));
return false;
}
return $body;
}
function postWithCurl(string $url, array $data, array $options = []): string|false {
$ch = curl_init();
$defaultOptions = [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true, // 生產環境務必驗證
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'PHP/' . PHP_VERSION . ' cURL/' . curl_version()['version'],
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
],
];
curl_setopt_array($ch, array_merge($defaultOptions, $options));
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($errno) {
error_log("cURL 錯誤 [$errno]: $error");
return false;
}
if ($httpCode < 200 || $httpCode >= 300) {
error_log("HTTP $httpCode: " . substr($response, 0, 200));
return false;
}
return $response;
}
// cURL 檔案上傳 (PHP 5.5+ 推薦 CURLFile)
$data = [
'field1' => 'value1',
'file' => new CURLFile('/path/to/file.jpg', 'image/jpeg', 'filename.jpg'),
];
postWithCurl('https://api.example.com/upload', $data);
// fsockopen 需手動構建 multipart body (複雜,不建議)
| 維度 | fsockopen | cURL |
|---|---|---|
| 依賴 | PHP 核心 (無擴展) | 需 cURL 擴展 (標準安裝) |
| SSL/TLS | 手動 ssl://、證書驗證複雜 | 內建完整支援、預設安全 |
| 協定支援 | HTTP/1.0/1.1 手動構建 | HTTP/1.1、HTTP/2、FTP、SMTP 等 |
| 重導向 | 手動解析 Location 再請求 | CURLOPT_FOLLOWLOCATION 自動 |
| Cookie 管理 | 手動解析 Set-Cookie | CURLOPT_COOKIEJAR/FILE 自動 |
| 多工/並發 | 需 stream_select 多路複用 | curl_multi_* 成熟 API |
| 效能 | 單請求略快 (無擴展開銷) | 高併發下 curl_multi 極佳 |
| 錯誤處理 | 需自行解析 | 豐富錯誤碼、資訊 |
| 維護性 | 代碼冗長、易出錯 | API 簡潔、標準化 |
class HttpClient {
private $useCurl = true;
public function __construct(bool $preferCurl = true) {
$this->useCurl = $preferCurl && extension_loaded('curl');
}
public function post(string $url, array $data, array $options = []): string|false {
return $this->useCurl
? postWithCurl($url, $data, $options)
: postWithFsockopen($url, $data, $options['timeout'] ?? 30);
}
public function get(string $url, array $params = [], array $options = []): string|false {
$url = $params ? $url . '?' . http_build_query($params) : $url;
// 類似實作 GET...
}
}
CURLOPT_SSL_VERIFYPEER = true)CURLOPT_MAXREDIRS = 5)http_build_query 自動編碼採用 cURL 為主、fsockopen 為備的策略,並封裝統一介面,能讓服務間通訊更穩健、可維護、安全。