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

PHP Socket 發送 HTTP 請求(GET/POST)完整封裝與實戰

     編輯:PHP 綜合知識

理解 HTTP 協議細節、排查網絡問題、或在無 cURL/流擴展環境下發送請求,掌握原生 Socket 發送 HTTP 請求至關重要。本文提供兩個健壯的封裝類(介面風格與單例風格),支援 GET/POST、自定義 Header、超時控制、錯誤處理。

HTTP 報文結構速覽

請求報文:
<請求行> GET /path?query HTTP/1.1
<請求頭> Host: example.com
          Content-Type: application/x-www-form-urlencoded
          Content-Length: 27
<空行>
<請求體> username=test&age=25

響應報文同理:狀態行、響應頭、空行、響應體

封裝一:介面風格(教學適合)

interface HttpProto {
    public function conn(string $url): void;
    public function get(): string;
    public function post(array $data = []): string;
    public function close(): void;
}

class HttpClient implements HttpProto {
    const CRLF = "\r\n";
    private $url, $fp, $response = '';
    private $line = [], $header = [], $body = [];
    private $version = 'HTTP/1.1';

    public function conn(string $url) {
        $this->url = parse_url($url);
        $this->url['port'] = $this->url['port'] ?? 80;
        $this->url['path'] = $this->url['path'] ?? '/';
        $this->url['query'] = $this->url['query'] ?? '';
        $this->fp = fsockopen($this->url['host'], $this->url['port'], $errno, $errstr, 5);
        if (!$this->fp) throw new RuntimeException("連接失敗: $errstr ($errno)");
        $this->header[] = 'Host: ' . $this->url['host'];
    }

    private function setLine(string $method) {
        $this->line = ["{$method} {$this->url['path']}?{$this->url['query']} {$this->version}"];
    }

    public function setHeader(string $line) { $this->header[] = $line; }

    private function setBody(array $data) { $this->body = [http_build_query($data)]; }

    private function request(): string {
        $req = array_merge($this->line, $this->header, [''], $this->body, ['']);
        $reqStr = implode(self::CRLF, $req);
        fwrite($this->fp, $reqStr);
        while (!feof($this->fp)) $this->response .= fread($this->fp, 4096);
        $this->close();
        return $this->response;
    }

    public function get(): string {
        $this->setLine('GET');
        return $this->request();
    }

    public function post(array $data = []): string {
        $this->setLine('POST');
        $this->setHeader('Content-Type: application/x-www-form-urlencoded');
        $this->setBody($data);
        $this->setHeader('Content-Length: ' . strlen($this->body[0]));
        return $this->request();
    }

    public function close() { if ($this->fp) fclose($this->fp); }
}

// 使用
$client = new HttpClient();
$client->conn('https://httpbin.org/get');
echo $client->get();

$client->conn('https://httpbin.org/post');
echo $client->post(['username' => '測試', 'age' => 25]);

封裝二:單例風格(生產更實用)

class HttpSocket {
    private $fp, $host, $port, $path, $query;
    private $headers = [], $body = '';
    private static $instance;

    private function __construct() {}
    public static function create(): self {
        return self::$instance ??= new self();
    }

    public function init(string $url): self {
        $u = parse_url($url);
        $this->host = $u['host'];
        $this->port = $u['port'] ?? 80;
        $this->path = $u['path'] ?? '/';
        $this->query = $u['query'] ?? '';
        $this->headers['Host'] = $this->host;
        return $this;
    }

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

    public function post(array $headers = [], array $data = []): string {
        $this->headers = array_merge($this->headers, $headers);
        if ($data) {
            $this->body = http_build_query($data);
            $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
            $this->headers['Content-Length'] = strlen($this->body);
        }
        return $this->request('POST');
    }

    private function request(string $method): string {
        $reqLine = "$method {$this->path}?{$this->query} HTTP/1.1";
        $headerStr = implode("\r\n", array_map(fn($k,$v)=>"$k: $v", array_keys($this->headers), $this->headers));
        $request = "$reqLine\r\n$headerStr\r\n\r\n$this->body";

        $this->fp = fsockopen($this->host, $this->port, $errno, $errstr, 5);
        if (!$this->fp) throw new RuntimeException("Socket 連接失敗: $errstr ($errno)");

        fwrite($this->fp, $request);
        $resp = '';
        while (!feof($this->fp)) $resp .= fread($this->fp, 4096);
        fclose($this->fp);
        return $resp;
    }
}

// 使用
$http = HttpSocket::create()->init('https://httpbin.org/post');
echo $http->post(
    ['User-Agent' => 'MyBot/1.0'],
    ['username' => '中文測試', 'age' => 22]
);

進階功能擴展建議

何時使用原生 Socket

  • 學習 HTTP 協議細節、教學演示。
  • 極度受限環境(禁用 cURL、allow_url_fopen=Off、無 streams)。
  • 需要精細控制 TCP 連接(Keep-Alive、自定義握手、原始協議)。
  • 調試代理、負載均衡、WAF 規則時發送畸形報文。
生產項目強烈建議使用 cURL 或 Guzzle/HTTP Client:功能完備、性能優化、安全性經過大規模驗證。Socket 封裝僅作為底層理解與特殊場景補充。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
功能實現要點
HTTPS 支援fsockopen('ssl://host', 443) 或 stream_socket_client('ssl://host:443'),需驗證證書(stream_context_set_option)
Chunked 編碼響應解析解析 Transfer-Encoding: chunked,按塊長度讀取拼接
Gzip/Deflate 解壓檢測 Content-Encoding,用 gzinflate()/gzdecode()
Cookie 管理解析 Set-Cookie,後續請求帶上 Cookie 頭
重定向跟隨解析 Location,遞歸請求(限制次數防循環)
多部分表單構造 multipart/form-data 請求體,支援文件上傳