首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP Socket 程式設計模擬 HTTP GET/POST 請求:底層實作與高層替代方案

     編輯:PHP 相關知識

PHP Socket 程式設計模擬 HTTP 請求

透過 `fsockopen()` 建立 TCP 連線,手工組裝 HTTP 請求報文,發送並解析回應,是理解 HTTP 協議、排查網路問題、無擴展環境下發送請求的基礎技能。同時介紹 cURL、Guzzle 等生產級替代方案。

原始 Socket 實作 (教學用)

<?php
class HttpSocket {
    private $host;
    private $port;
    private $timeout = 10;
    private $fp;

    public function __construct(string $host, int $port = 80) {
        $this->host = $host;
        $this->port = $port;
    }

    public function request(string $method, string $path, array $headers = [], string $body = ''): string {
        // 1. 建立連線
        $this->fp = fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
        if (!$this->fp) {
            throw new RuntimeException("Connect failed: $errstr ($errno)");
        }
        stream_set_timeout($this->fp, $this->timeout);

        // 2. 組裝請求行
        $request = "$method $path HTTP/1.1\r\n";

        // 3. 組裝標頭
        $headers['Host'] = $headers['Host'] ?? $this->host;
        $headers['Connection'] = $headers['Connection'] ?? 'close';
        $headers['User-Agent'] = $headers['User-Agent'] ?? 'PHP-Socket/1.0';
        if ($body) {
            $headers['Content-Length'] = $headers['Content-Length'] ?? strlen($body);
            $headers['Content-Type'] = $headers['Content-Type'] ?? 'application/x-www-form-urlencoded';
        }
        foreach ($headers as $k => $v) {
            $request .= "$k: $v\r\n";
        }
        $request .= "\r\n"; // 空行分隔標頭與主體
        $request .= $body;

        // 4. 發送
        fwrite($this->fp, $request);

        // 5. 讀取回應
        $response = '';
        while (!feof($this->fp)) {
            $response .= fread($this->fp, 8192);
        }
        fclose($this->fp);
        return $response;
    }

    public function get(string $path, array $headers = []): string {
        return $this->request('GET', $path, $headers);
    }

    public function post(string $path, array $data, array $headers = []): string {
        $body = http_build_query($data);
        return $this->request('POST', $path, $headers, $body);
    }
}

// 使用範例
$client = new HttpSocket('httpbin.org', 80);
// GET
echo $client->get('/get');
// POST
echo $client->post('/post', ['username' => 'test', 'action' => 'login']);
?>

關鍵細節解析

  • 請求行格式:`METHOD /path?query HTTP/1.1\r\n` (HTTP/1.1 必須有 Host 標頭)。
  • 標頭結束:雙 `\r\n` (空行) 分隔標頭與主體。
  • Content-Length**:POST 必須準確指定主體位元組長度。
  • Chunked Transfer**:若回應含 `Transfer-Encoding: chunked`,需手工解碼區塊 (本簡易版未處理)。
  • HTTPS**:`fsockopen('ssl://host', 443)` 或 `stream_socket_client('tls://host:443')`,需驗證憑證 (生產環境強制)。
  • 持久連線**:`Connection: keep-alive` 需手工管理連線複用、管線化、閒置超時,複雜度高,**建議直接用 cURL**。

生產級替代方案比較

方案優點缺點適用場景
cURL (PHP 擴展)功能完整 (HTTPS、代理、憑證、Cookie、多執行緒、HTTP/2)、效能高、C 實現需安裝擴展、API 較繁瑣絕大多數生產環境 HTTP 客戶端
Guzzle (Composer 套件)PSR-7/18 相容、中介軟體、異步、重試、測試雙工、文件豐富依賴 Composer、增加依賴體積現代 PHP 專案、微服務呼叫、API 客戶端
Symfony HttpClient原生支援非同步、Scoping Client、契約測試、整合 Symfony 生態學習曲線稍高Symfony/Laravel 專案、長期維護專案
原生 Socket零依賴、教學價值高、極度精簡環境可用需自行處理 HTTPS、Chunked、Keep-Alive、重導向、錯誤重試、超時細節教學、嵌入式/受限環境、協議除錯

cURL 簡易封裝 (生產常用)

<?php
function http_request(string $url, string $method = 'GET', array $data = [], array $headers = [], int $timeout = 10): array {
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_SSL_VERIFYPEER => true, // 生產必須驗證
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_HTTPHEADER => array_map(fn($k,$v)=>"$k: $v", array_keys($headers), $headers),
        CURLOPT_HEADER => true, // 回應含標頭
    ]);
    if ($data) {
        if ($method === 'GET') {
            $url .= (strpos($url, '?') ? '&' : '?') . http_build_query($data);
            curl_setopt($ch, CURLOPT_URL, $url);
        } else {
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
        }
    }
    $response = curl_exec($ch);
    $errno = curl_errno($ch);
    $error = curl_error($ch);
    $info = curl_getinfo($ch);
    curl_close($ch);

    if ($errno) {
        return ['success' => false, 'error' => $error, 'errno' => $errno];
    }
    $headerSize = $info['header_size'];
    $respHeaders = substr($response, 0, $headerSize);
    $body = substr($response, $headerSize);
    return [
        'success' => true,
        'status' => $info['http_code'],
        'headers' => $respHeaders,
        'body' => $body,
        'info' => $info,
    ];
}
?>

常見坑點與除錯

  • 回應截斷**:`feof()` 在非阻塞/SSL 下可能誤判,改用 `stream_get_contents()` 或 cURL `CURLOPT_RETURNTRANSFER`。
  • 編碼問題**:請求主體與標頭必須為 ASCII/ISO-8859-1,中文參數需 `urlencode()`;回應依 `Content-Type` charset 解碼。
  • 重導向**:Socket 手工處理 301/302 `Location` 標頭;cURL 設定 `CURLOPT_FOLLOWLOCATION => true`。
  • 大檔案上傳**:`CURLOPT_INFILE` + `CURLOPT_UPLOAD` 或 `CURLOPT_READFUNCTION` 串流讀取,避免記憶體爆炸。

理解 Socket 底層原理有助於深度除錯,但**生產環境請務必使用 cURL 或 Guzzle 等成熟庫**,專注業務邏輯而非協議細節。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved