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

PHP 使用 Socket 模擬 HTTP POST/GET 請求:fsockopen 底層實作完整範例

     編輯:PHP 綜合知識

應用場景

  • 無 cURL 擴充套件的環境。
  • 需精確控制 HTTP 協定細節(自定 Header、Chunked 傳輸、Keep-Alive)。
  • 教學理解 HTTP 協定運作。

通用 Socket HTTP Client 函式

<?php
/**
 * 使用 fsockopen 發送 HTTP 請求
 *
 * @param string $url      完整 URL(含 scheme、host、path、query)
 * @param array|string $postData POST 欄位陣列或已編碼字串,空字串為 GET
 * @param array $headers   額外 Header 陣列 ['Header-Name' => 'Value']
 * @param int $timeout     連線逾時秒數
 * @return string|false    回應內容(含 Header + Body),失敗回 false
 */
function socketHttpRequest(string $url, $postData = '', array $headers = [], int $timeout = 10)
{
    $parsed = parse_url($url);
    if (!$parsed) {
        return false;
    }

    $scheme = $parsed['scheme'] ?? 'http';
    $host   = $parsed['host'] ?? '';
    $port   = $parsed['port'] ?? ($scheme === 'https' ? 443 : 80);
    $path   = ($parsed['path'] ?? '/') . ($parsed['query'] ? '?' . $parsed['query'] : '');

    // 建立連線(HTTPS 需 ssl:// 前綴且啟用 openssl 擴充套件)
    $fp = @fsockopen(($scheme === 'https' ? 'ssl://' : '') . $host, $port, $errno, $errstr, $timeout);
    if (!$fp) {
        error_log("fsockopen 失敗 [$errno]: $errstr");
        return false;
    }

    stream_set_timeout($fp, $timeout);

    // 組裝請求
    $isPost = is_array($postData) || $postData !== '';
    $method = $isPost ? 'POST' : 'GET';

    $request = "$method $path HTTP/1.1\r\n";
    $request .= "Host: $host\r\n";
    $request .= "Connection: Close\r\n"; // 簡化:每次請求關閉連線
    $request .= "User-Agent: PHP-Socket-Client/1.0\r\n";

    if ($isPost) {
        $body = is_array($postData) ? http_build_query($postData) : $postData;
        $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $request .= "Content-Length: " . strlen($body) . "\r\n";
    }

    // 自定 Header
    foreach ($headers as $name => $value) {
        $request .= "$name: $value\r\n";
    }

    $request .= "\r\n"; // Header 結束

    if ($isPost) {
        $request .= $body;
    }

    // 發送
    fwrite($fp, $request);

    // 接收回應
    $response = '';
    while (!feof($fp)) {
        $response .= fread($fp, 8192);
    }
    fclose($fp);

    return $response;
}

// 解析回應:分離 Header 與 Body
function parseHttpResponse(string $response): array
{
    $headerEnd = strpos($response, "\r\n\r\n");
    if ($headerEnd === false) {
        return ['headers' => '', 'body' => $response];
    }
    $headers = substr($response, 0, $headerEnd);
    $body    = substr($response, $headerEnd + 4);
    return ['headers' => $headers, 'body' => $body];
}

// === 使用範例 ===
// GET 請求
$resp = socketHttpRequest('https://httpbin.org/get?foo=bar');
$data = parseHttpResponse($resp);
echo "GET Body:\n" . $data['body'] . "\n";

// POST 請求
$resp = socketHttpRequest('https://httpbin.org/post', ['name' => 'John', 'age' => 30]);
$data = parseHttpResponse($resp);
echo "POST Body:\n" . $data['body'] . "\n";
?>

關鍵點解析

步驟說明
parse_url解析 URL 取得 host、port、path、query。
fsockopen建立 TCP 連線,HTTPS 用 ssl://host:443(需 openssl)。
HTTP/1.1 請求格式請求行 + Headers + 空行 + Body。\r\n 為換行標準。
Content-LengthPOST 必須指定,否則伺服器不知何時結束讀取 Body。
Connection: Close避免 Keep-Alive 處理複雜性,教學用途足夠。
stream_set_timeout防止讀取卡死。
fread 迴圈完整讀取直到 EOF。

生產環境建議改用

  • cURL:curl_exec() 功能完整、效能優、支援 HTTP/2、多工、憑證驗證。
  • GuzzleHttp:物件導向、PSR-7/18 相容、中介軟體、重試、異步。
  • PHP 內建 stream_context:file_get_contents() + stream_context_create() 簡單 GET/POST。

安全提醒

  • HTTPS 必須驗證憑證(stream_context_set_option($ctx, 'ssl', 'verify_peer', true)),否則遭受 MITM 攻擊。
  • 使用者輸入拼接 URL 時需 parse_url 驗證 scheme/host,防 SSRF。
  • 回應內容不可直接 eval/unserialize,防反序列化攻擊。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved