早期 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 呼叫範例,開箱即用。
| 方法/屬性 | 說明 |
|---|---|
__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 / $statusText | HTTP 狀態碼與文字 |
<?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;
}
<%@ 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
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;
}
HttpRequest 類以純 PHP 實現 ASP XMLHTTP 核心語意,涵蓋連線管理、Header 操作、內容編碼自動解碼、超時控制,並提供完整跨語言驗證範例。理解其內部機制有助於排查舊系統整合疑難雜症;新專案請直接採用 Guzzle 等現代化客戶端庫。