stream_context + file_get_contentsHttpClient::init(),鏈式調用友好<?php
class HttpClient {
public ?string $buffer = null; // 回應內容
public ?string $referer = null; // 請求 Referer
public ?string $response = null; // 回應 Header(原始字串)
public ?string $request = null; // 請求 Header(原始字串)
private array $args = [];
/** 靜態工廠 */
public static function init(?array $args = []): self {
return new self($args);
}
private function __construct(array $args = []) {
$this->args = $args;
if (!empty($this->args['debugging'])) {
ob_end_clean();
set_time_limit(0);
header('Content-Type: text/plain; charset=utf-8');
}
}
/** 組裝通用 Header */
private function buildHeader(string $host, ?string $cookie = null, ?string $contentType = null, ?int $contentLength = null): string {
$ua = $this->args['userAgent'] ?? $_SERVER['HTTP_USER_AGENT'] ?? 'PHP-HttpClient/1.0';
$header = "Host: {$host}\r\n";
$header .= "Connection: close\r\n";
$header .= "Accept: */*\r\n";
$header .= "User-Agent: {$ua}\r\n";
$header .= "DNT: 1\r\n";
if ($cookie) $header .= "Cookie: {$cookie}\r\n";
if ($this->referer) $header .= "Referer: {$this->referer}\r\n";
if ($contentType) $header .= "Content-Type: {$contentType}\r\n";
if ($contentLength) $header .= "Content-Length: {$contentLength}\r\n";
return $header;
}
/** GET 請求 */
public function get(string $url, ?string $query = null, ?string $cookie = null): ?string {
$parsed = parse_url($url);
$host = $parsed['host'] ?? '';
$url .= (isset($parsed['query']) ? '&' : '?') . $query;
$header = $this->buildHeader($host, $cookie);
$this->request = $header;
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => $header,
'timeout' => $this->args['timeout'] ?? 30,
'ignore_errors' => true,
],
]);
$this->response = implode("\r\n", get_headers($url, 1) ?? []);
$this->buffer = @file_get_contents($url, false, $context);
return $this->buffer;
}
/** POST 請求 */
public function post(string $url, ?string $data = null, ?string $cookie = null): ?string {
$parsed = parse_url($url);
$host = $parsed['host'] ?? '';
$header = $this->buildHeader($host, $cookie, 'application/x-www-form-urlencoded', $data ? strlen($data) : null);
$this->request = $header;
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => $header,
'content' => $data,
'timeout' => $this->args['timeout'] ?? 30,
'ignore_errors' => true,
],
]);
$this->response = implode("\r\n", get_headers($url, 1) ?? []);
$this->buffer = @file_get_contents($url, false, $context);
return $this->buffer;
}
}
<?php
require 'HttpClient.php';
$client = HttpClient::init([
'debugging' => true,
'userAgent' => 'MyBot/2.0',
'timeout' => 15,
]);
// 簡單 GET
$client->get('https://www.baidu.com', 'wd=php');
echo "=== Request Header ===\n{$client->request}\n";
echo "=== Response Header ===\n{$client->response}\n";
echo "=== Body (first 500) ===\n" . mb_substr($client->buffer, 0, 500) . "\n";
// 攜帶 Cookie 與 Referer
$client->referer = 'https://www.google.com/';
$client->get('https://httpbin.org/cookies/set', 'session=abc123', 'uid=1; role=admin');
echo $client->buffer; // httpbin 回顯設定的 Cookie
// POST 表單
$client->post('https://httpbin.org/post', 'username=john&password=secret');
echo $client->buffer;
| 優點 | 缺點 |
|---|---|
| 零擴展依賴,PHP 5.3+ 即可運行 | 無法精細控制 SSL 憑證驗證(需 cURL) |
| 代碼極簡,易嵌入舊專案 | 不支援並發請求 |
| 自動記錄 Header,除錯方便 | 大檔案下載會全量載入記憶體 |
| 支援自定義 User-Agent、Cookie、Referer | 重定向處理依賴 get_headers,非透明 |
postJson($url, $data) 內部 json_encode + 設定 Content-Type: application/json。multipart/form-data body,需手動拼裝 boundary。$this->response 解析 HTTP 狀態碼,非 2xx 拋出 HttpException。Psr\Http\Client\ClientInterface,無縫接入現代框架。beforeRequest/afterResponse 回調,統一處理簽名、日誌、重試。這個 HttpClient 類適合「不想引入 Composer 依賴、只需基本 GET/POST、需快速除錯 Header」的輕量場景。生產環境高併發、複雜 TLS、並發需求請直接使用 Guzzle 或 Symfony HttpClient。理解 stream_context 機制,也能在無 cURL 環境(如某些受限虛擬主機)下優雅完成 HTTP 交互。