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

PHP 模擬 ASP XMLHTTP 元件:HttpRequest 類完整實現與跨語言互操作範例

     編輯:PHP 相關知識

背景與動機

早期 PHP 專案常需整合舊有 ASP 系統,或爬取 ASP 撰寫的介面。ASP 經典 MSXML2.XMLHTTP / WinHttp.WinHttpRequest 行為細節(Header 處理、Gzip 解壓、Chunked 編碼、Cookie 管理)若手工用 fsockopen/stream_context 重寫極易遺漏邊界情況。本 HttpRequest 類完整模擬 XMLHTTP 介面,支援 GET/POST、自定 Header、超時、Gzip/Chunked 透明解碼、Socket/Stream 雙模式,並附 ASP 測試端與 PHP 呼叫範例,開箱即用。

HttpRequest 類核心 API

方法/屬性說明
__construct($url, $method='GET', $useSocket=0)建構子,$useSocket=1 強制用 Socket,0 用 fsockopen
open($ip='', $port=-1)建立連線,可指定真實 IP/Port 繞過 DNS
setRequestHeader($key, $value)設定請求 Header(Host/Connection/User-Agent 已自動處理)
send($data='')發送請求,GET 時 $data 自動轉 Query String
getResponseBody()取得回應本文(自動處理 Gzip/Chunked)
getAllResponseHeaders()取得所有回應 Header 原始字串
getResponseHeader($key)取得單一回應 Header(支援 Set-Cookie 多值合併)
setTimeout($sec)設定讀取超時(秒),需在 send() 前呼叫
$status / $statusTextHTTP 狀態碼與文字

完整類代碼(inc_http.php)

<?php
class HttpRequest {
    public $url, $method, $port, $hostname, $uri, $protocol;
    public $exception, $status, $statusText, $httpProtocolVersion;
    private $fp=0, $_buffer='', $responseBody, $responseHeader, $timeout=0, $useSocket;
    private $_headers=[], $_sendData;

    public function __construct($url='', $method='GET', $useSocket=0) {
        $this->url = $url;
        $this->method = strtoupper($method);
        $this->useSocket = $useSocket;
        // 預設 Header 模擬瀏覽器
        $this->setRequestHeader('Accept', '*/*');
        $this->setRequestHeader('Accept-Language', 'zh-CN');
        $this->setRequestHeader('Accept-Encoding', 'gzip, deflate');
        $this->setRequestHeader('User-Agent', 'HttpRequest Class 1.0');
    }

    public function open($ip='', $port=-1) {
        if (!$this->_parseUrl()) return false;
        $this->setRequestHeader('Host', $this->hostname);
        $this->setRequestHeader('Connection', 'close');
        $ip   = $ip ?: $this->hostname;
        $port = $port == -1 ? $this->port : $port;

        if ($this->useSocket) {
            if (!($this->fp = socket_create(AF_INET, SOCK_STREAM, 0))) {
                $this->exception = 'Create socket failed'; return false;
            }
            if (!socket_connect($this->fp, $ip, $port)) {
                $this->exception = "Connect to {$this->hostname}:{$port} failed"; return false;
            }
        } else {
            if (!($this->fp = fsockopen($ip, $port, $errno, $errstr, 10))) {
                $this->exception = "fsockopen failed: $errstr ($errno)"; return false;
            }
        }
        return true;
    }

    public function send($data='') {
        if (!$this->fp) { $this->exception='Invalid socket'; return false; }
        if ($this->method === 'GET' && $data !== '') {
            $sep = strpos($this->uri, '?') ? '&' : '?';
            $this->uri .= $sep . $data;
            $data = '';
        }
        if ($this->method === 'POST') {
            $this->setRequestHeader('Content-Length', strlen($data));
            $this->setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        }
        $req = "{$this->method} {$this->uri} HTTP/1.1\r\n";
        foreach ($this->_headers as $k => $v) $req .= "$k: $v\r\n";
        $req .= "\r\n";
        if ($this->method === 'POST') $req .= $data;
        $this->_sendData = $req;

        $start = time();
        if ($this->useSocket) {
            socket_write($this->fp, $req);
            $buf = '';
            do {
                if ($this->timeout && time()-$start > $this->timeout) break;
                $chunk = socket_read($this->fp, 4096);
                $buf .= $chunk;
            } while ($chunk !== '');
            socket_close($this->fp);
        } else {
            fwrite($this->fp, $req);
            $buf = '';
            while (!feof($this->fp)) {
                if ($this->timeout && time()-$start > $this->timeout) break;
                $buf .= fgets($this->fp, 4096);
            }
            fclose($this->fp);
        }
        $this->_buffer = $buf;
        $this->_splitResponse();
        $this->_parseStatusLine();
    }

    public function getResponseBody() {
        $ce = $this->getResponseHeader('Content-Encoding');
        $te = $this->getResponseHeader('Transfer-Encoding');
        $body = $this->responseBody;
        if ($ce === 'gzip' && $te === 'chunked') return gzdecode_1(transfer_encoding_chunked_decode($body));
        if ($ce === 'gzip') return gzdecode_1($body);
        return $body;
    }
    public function getAllResponseHeaders() { return $this->responseHeader; }
    public function getResponseHeader($key) {
        $key = strtolower($key);
        $hdr = $this->responseHeader . "\r\n";
        if (preg_match_all("/\n$key:(.+?)\r/is", $hdr, $m, PREG_SET_ORDER)) {
            $out = '';
            foreach ($m as $v) {
                $val = ltrim($v[1]);
                if ($key === 'set-cookie') $val = substr($val, 0, strpos($val, ';'));
                $out .= "$val; ";
            }
            return rtrim($out, '; ');
        }
        return '';
    }
    public function setTimeout($t) { $this->timeout = $t; }
    public function setRequestHeader($k, $v='') { $this->_headers[$k] = $v; }
    public function removeRequestHeader($k) {
        if (isset($this->_headers[$k])) unset($this->_headers[$k]);
    }

    // === 私有輔助 ===
    private function _parseUrl() {
        if (preg_match('/^http:\/\/([^:\/]+)(:(\d+))?\/(.*)$/i', $this->url, $m)) {
            $this->hostname = $m[1];
            $this->port = isset($m[3]) ? (int)$m[3] : 80;
            $this->uri = '/' . $m[4];
            $this->protocol = 'http';
            return true;
        }
        if (preg_match('/^http:\/\/([^:\/]+)(:(\d+))?(\/)?$/i', $this->url, $m)) {
            $this->hostname = $m[1];
            $this->port = isset($m[3]) ? (int)$m[3] : 80;
            $this->uri = '/';
            $this->protocol = 'http';
            return true;
        }
        $this->exception = 'URL format error'; return false;
    }
    private function _splitResponse() {
        $p = strpos($this->_buffer, "\r\n\r\n");
        if ($p !== false) {
            $this->responseHeader = substr($this->_buffer, 0, $p);
            $this->responseBody = substr($this->_buffer, $p+4);
        }
    }
    private function _parseStatusLine() {
        if (preg_match('/^HTTP\/(.+?)\s(\d+)\s(.+?)\r/is', $this->responseHeader, $m)) {
            $this->httpProtocolVersion = $m[1];
            $this->status = (int)$m[2];
            $this->statusText = $m[3];
        }
    }
}

// === Gzip / Chunked 解碼輔助函式 ===
function gzdecode_1($data) {
    if (function_exists('gzdecode')) return gzdecode($data);
    $flags = ord($data[3]); $hlen = 10;
    if ($flags & 4) { $ex = unpack('v', substr($data,10,2)); $hlen += 2 + $ex[1]; }
    if ($flags & 8) $hlen = strpos($data, chr(0), $hlen) + 1;
    if ($flags & 16) $hlen = strpos($data, chr(0), $hlen) + 1;
    if ($flags & 2) $hlen += 2;
    return @gzinflate(substr($data, $hlen)) ?: $data;
}
function transfer_encoding_chunked_decode($in) {
    $out = '';
    while ($in !== '') {
        $lf = strpos($in, "\n");
        if ($lf === false) { $out .= $in; break; }
        $line = trim(substr($in, 0, $lf));
        $sc = strpos($line, ';');
        if ($sc !== false) $line = substr($line, 0, $sc);
        if ($line === '') { $in = substr($in, $lf+1); continue; }
        $len = hexdec($line);
        if ($len) {
            $out .= substr($in, $lf+1, $len);
            $in = substr($in, $lf+2+$len);
        } else { $in = ''; }
    }
    return $out;
}

ASP 測試端

<%@ Language=VBScript %>
<%
Response.Cookies("a") = "anlige"
Response.Cookies("a").Expires = DateAdd("yyyy", 1, Now())
Response.Cookies("b")("c") = "wsdasdadsa"
Response.Cookies("b")("d") = "ddd"
Response.Cookies("b").Expires = DateAdd("yyyy", 1, Now())
Response.Write "QueryString: " & Request.QueryString & "<br/>"
For Each v In Request.QueryString
    Response.Write v & "=" & Request.QueryString(v) & "<br/>"
Next
Response.Write "<br/>Form: " & Request.Form & "<br/>"
For Each v In Request.Form
    Response.Write v & "=" & Request.Form(v) & "<br/>"
Next
Response.Write "<br/>URL: " & Request.ServerVariables("URL") & "<br/>"
Response.Write "Referer: " & Request.ServerVariables("HTTP_REFERER") & "<br/>"
Response.Write "Host: " & Request.ServerVariables("HTTP_HOST") & "<br/>"
Response.Write "User-Agent: " & Request.ServerVariables("HTTP_USER_AGENT") & "<br/>"
Response.Write "Cookie: " & Request.ServerVariables("HTTP_COOKIE")
%>

PHP 呼叫範例

<?php
require 'inc_http.php';
$url = 'https://dev.example.com/asp/response.asp';

$act = $_GET['action'] ?? '';
switch ($act) {
    case 'get':
        $http = new HttpRequest("$url?a=text");
        $http->open();
        $http->send('name=anlige&city=' . urlencode('杭州'));
        echo $http->getResponseBody();
        break;
    case 'post':
        $http = new HttpRequest("$url?a=text", 'POST');
        $http->open();
        $http->send('name=anlige&city=' . urlencode('杭州'));
        echo $http->getResponseBody();
        break;
    case 'header_referer':
        $http = new HttpRequest("$url?a=text", 'POST');
        $http->open();
        $http->setRequestHeader('Referer', 'https://www.baidu.com');
        $http->send('name=anlige&city=' . urlencode('杭州'));
        echo $http->getResponseBody();
        break;
    case 'header_useragent':
        $http = new HttpRequest("$url?a=text", 'POST');
        $http->open();
        $http->setRequestHeader('Referer', 'https://www.baidu.com');
        $http->setRequestHeader('User-Agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)');
        $http->send('name=anlige&city=' . urlencode('杭州'));
        echo $http->getResponseBody();
        break;
    case 'status':
        $http = new HttpRequest("$url?a=text", 'POST');
        $http->open();
        $http->send('name=anlige&city=' . urlencode('杭州'));
        echo "{$http->status} {$http->statusText}<br/><br/>";
        echo $http->getResponseBody();
        break;
    case 'get_headers':
        $http = new HttpRequest("$url?a=text", 'GET');
        $http->open();
        $http->send('name=anlige&city=' . urlencode('杭州'));
        echo $http->getAllResponseHeaders() . "<br/><br/>";
        echo 'Server: ' . $http->getResponseHeader('Server') . "<br/><br/>";
        break;
    case 'get_image':
        $http = new HttpRequest('https://www.baidu.com/img/baidu_logo.gif');
        $http->open();
        $http->send();
        file_put_contents('demo.gif', $http->getResponseBody());
        echo '<img src="demo.gif" />';
        break;
}

現代化替代建議

  • GuzzleHttp (PHP 5.5+): 物件導向、Middleware、Promise 非同步、PSR-7/18 標準、測試友善,新專案首選。
  • Symfony HttpClient / HTTPlug: 框架整合、契約導向。
  • cURL 原生擴充: 底層效能最高,支援 HTTP/2、多路復用,但 API 較繁瑣。
  • 本類適用場景: 維護舊專案、無法安裝 Composer 套件、需精確模擬 ASP XMLHTTP 行為(含 Chunked/Gzip 透明處理)的特定整合案例。

總結

HttpRequest 類以純 PHP 實現 ASP XMLHTTP 核心語意,涵蓋連線管理、Header 操作、內容編碼自動解碼、超時控制,並提供完整跨語言驗證範例。理解其內部機制有助於排查舊系統整合疑難雜症;新專案請直接採用 Guzzle 等現代化客戶端庫。

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