file_get_contents 配合 Stream Context 可發送 POST 請求,無需額外擴展。但功能有限、錯誤處理繁瑣。本文提供完整參數範例、JSON/表單雙模式、超時控制、重試邏輯、最後推薦生產環境使用 Guzzle 或 Http\Client (PSR-18)。
$url = 'https://api.example.com/endpoint';
$data = ['name' => 'Joe', 'website' => 'www.example.com'];
$options = [
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => http_build_query($data),
'timeout' => 10, // 秒
'ignore_errors' => true, // 取得 4xx/5xx 回應內容
],
];
$context = stream_context_create($options);
$response = @file_get_contents($url, false, $context);
if ($response === false) {
$error = error_get_last();
throw new RuntimeException("Request failed: {$error['message']}");
}
$httpCode = 0;
if (isset($http_response_header)) {
foreach ($http_response_header as $header) {
if (stripos($header, 'HTTP/') === 0) {
$httpCode = (int)explode(' ', $header)[1];
break;
}
}
}
if ($httpCode >= 400) {
throw new RuntimeException("HTTP $httpCode: $response");
}
$result = json_decode($response, true);
$data = ['name' => 'Joe', 'website' => 'www.example.com'];
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$options = [
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\nContent-Length: " . strlen($json),
'content' => $json,
'timeout' => 10,
'ignore_errors' => true,
],
];
$context = stream_context_create($options);
$response = @file_get_contents($url, false, $context);
// ... 同樣錯誤處理
function httpPost(string $url, array $data, array $options = []): array
{
$defaults = [
'timeout' => 10,
'json' => false, // true=JSON, false=form
'headers' => [],
'proxy' => null, // 'tcp://proxy:8080'
'retries' => 0,
'retry_delay' => 100000, // microseconds
];
$opts = array_merge($defaults, $options);
$content = $opts['json'] ? json_encode($data, JSON_UNESCAPED_UNICODE) : http_build_query($data);
$contentType = $opts['json'] ? 'application/json' : 'application/x-www-form-urlencoded';
$headers = [
"Content-Type: $contentType",
"Content-Length: " . strlen($content),
];
foreach ($opts['headers'] as $k => $v) {
$headers[] = "$k: $v";
}
$contextOpts = [
'http' => [
'method' => 'POST',
'header' => implode("\r\n", $headers) . "\r\n",
'content' => $content,
'timeout' => $opts['timeout'],
'ignore_errors' => true,
],
];
if ($opts['proxy']) {
$contextOpts['http']['proxy'] = $opts['proxy'];
$contextOpts['http']['request_fulluri'] = true;
}
$lastError = null;
for ($attempt = 0; $attempt <= $opts['retries']; $attempt++) {
$context = stream_context_create($contextOpts);
$response = @file_get_contents($url, false, $context);
if ($response !== false) {
$httpCode = 0;
if (isset($http_response_header)) {
foreach ($http_response_header as $h) {
if (stripos($h, 'HTTP/') === 0) {
$httpCode = (int)explode(' ', $h)[1];
break;
}
}
}
if ($httpCode < 400) {
return [
'code' => $httpCode,
'body' => $response,
'headers' => $http_response_header ?? [],
];
}
$lastError = "HTTP $httpCode: $response";
} else {
$lastError = error_get_last()['message'] ?? 'Unknown error';
}
if ($attempt < $opts['retries']) {
usleep($opts['retry_delay']);
}
}
throw new RuntimeException("Request failed after {$opts['retries']} retries: $lastError");
}
// 使用
try {
$result = httpPost('https://api.example.com/users', ['email' => 'test@example.com'], [
'json' => true,
'timeout' => 15,
'headers' => ['Authorization' => 'Bearer ' . $token],
'retries' => 2,
]);
$user = json_decode($result['body'], true);
} catch (RuntimeException $e) {
// 記錄日誌、回傳錯誤
}
| 問題 | 原因 | 解法 |
|---|---|---|
| HTTPS 憑證驗證失敗 | 預設驗證 CA,本地缺憑證或自簽 | 'ssl' => ['verify_peer' => false, 'verify_peer_name' => false] (僅測試用,生產必須驗證) |
| 超時無效 | timeout 僅適用連線/讀取,不含 DNS | 使用 stream_context_set_option($ctx, 'http', 'timeout', 10) 或 Guzzle |
| 大檔案上傳記憶體爆炸 | content 需完整載入記憶體 | 改用 fopen('php://temp') 串流上傳或 Guzzle Multipart |
| 無法取得回應 Header | 需依賴 $http_response_header 全域變數 | 函式結束後立即讀取,或用 Guzzle 物件導向存取 |
| 並發請求阻塞 | 同步阻塞 IO | 使用 Swoole/Fiber/Guzzle Promise 非同步 |
composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'timeout' => 10,
'base_uri' => 'https://api.example.com/',
]);
try {
$response = $client->post('endpoint', [
'json' => ['name' => 'Joe'],
'headers' => ['Authorization' => 'Bearer ' . $token],
]);
$data = json_decode($response->getBody()->getContents(), true);
$status = $response->getStatusCode();
} catch (RequestException $e) {
if ($e->hasResponse()) {
$status = $e->getResponse()->getStatusCode();
$body = $e->getResponse()->getBody()->getContents();
}
// 記錄錯誤
}
// 非同步並發
$promises = [
'users' => $client->getAsync('users'),
'posts' => $client->getAsync('posts'),
];
$results = GuzzleHttp\Promise\Utils::unwrap($promises);
file_get_contents 適合「無 Composer 環境、極簡腳本、單次請求」。凡涉及「重試、並發、串流、驗證、中介軟體、測試模擬」,請直接使用 Guzzle 或 Symfony HttpClient,獲得完整 HTTP 客戶端功能與 PSR-7/18 互通性。