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

PHP 反向代理類完整實現:支持文件上傳、IP 指定、SAE 環境適配

     編輯:PHP 相關知識

PHP 反向代理類完整實現:支持文件上傳、IP 指定、SAE 環境適配

基於 PHP Reverse Proxy (PRP) 修改優化,修復原版錯誤,新增文件上傳支持、文件類型識別、指定後端 IP、SAE 環境適配等功能。適用於 API 網關、跨域代理、內網穿透等場景。

一、快速使用示例

<?php
$proxy = new PhpReverseProxy();
$proxy->port = "8080";
$proxy->host = "www.example.com";
// $proxy->ip = "1.1.1.1"; // 可選:指定後端 IP
$proxy->forward_path = "";
$proxy->connect();
$proxy->output();

二、PhpReverseProxy 核心類完整代碼

<?php
// Source Code: https://www.xiumu.org/technology/php-reverse-proxy-class.shtml
class PhpReverseProxy {
    public $publicBaseURL;
    public $outsideHeaders;
    public $XRequestedWith;
    public $sendPost;
    public $port, $host, $ip, $content, $forward_path, $content_type, $user_agent,
           $XFF, $request_method, $IMS, $cacheTime, $cookie, $authorization;
    private $http_code, $lastModified, $version, $resultHeader;
    const chunkSize = 10000;

    function __construct() {
        $this->version = "PHP Reverse Proxy (PRP) 1.0";
        $this->port = "8080";
        $this->host = "127.0.0.1";
        $this->ip = "";
        $this->content = "";
        $this->forward_path = "";
        $this->path = "";
        $this->content_type = "";
        $this->user_agent = "";
        $this->http_code = "";
        $this->XFF = "";
        $this->request_method = "GET";
        $this->IMS = false;
        $this->cacheTime = 72000;
        $this->lastModified = gmdate("D, d M Y H:i:s", time()-72000) . " GMT";
        $this->cookie = "";
        $this->XRequestedWith = "";
        $this->authorization = "";
    }

    function translateURL($serverName) {
        $this->path = $this->forward_path . $_SERVER['REQUEST_URI'];
        if (defined('IS_SAE') && IS_SAE) return $this->translateServer($serverName) . $this->path;
        if ($_SERVER['QUERY_STRING'] == "") return $this->translateServer($serverName) . $this->path;
        else return $this->translateServer($serverName) . $this->path . "?" . $_SERVER['QUERY_STRING'];
    }

    function translateServer($serverName) {
        $s = empty($_SERVER["HTTPS"]) ? '' : ($_SERVER["HTTPS"] == "on") ? "s" : "";
        $protocol = $this->left(strtolower($_SERVER["SERVER_PROTOCOL"]), "/") . $s;
        if ($this->port == "") return $protocol . "://" . $serverName;
        else return $protocol . "://" . $serverName . ":" . $this->port;
    }

    function left($s1, $s2) {
        return substr($s1, 0, strpos($s1, $s2));
    }

    function preConnect() {
        $this->user_agent = $_SERVER['HTTP_USER_AGENT'];
        $this->request_method = $_SERVER['REQUEST_METHOD'];
        $tempCookie = "";
        foreach ($_COOKIE as $i => $value) {
            $tempCookie .= " $i=$_COOKIE[$i];";
        }
        $this->cookie = $tempCookie;
        if (empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $this->XFF = $_SERVER['REMOTE_ADDR'];
        } else {
            $this->XFF = $_SERVER['HTTP_X_FORWARDED_FOR'] . ", " . $_SERVER['REMOTE_ADDR'];
        }
    }

    function connect() {
        if (empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
            $this->preConnect();
            $ch = curl_init();
            if ($this->request_method == "POST") {
                curl_setopt($ch, CURLOPT_POST, 1);
                $postData = array();
                $filePost = false;
                $uploadPath = 'uploads/';
                if (defined('IS_SAE') && IS_SAE) $uploadPath = SAE_TMP_PATH;
                if (count($_FILES) > 0) {
                    if (!is_writable($uploadPath)) {
                        die('You cannot upload to the specified directory, please CHMOD it to 777.');
                    }
                    foreach ($_FILES as $key => $fileArray) {
                        copy($fileArray["tmp_name"], $uploadPath . $fileArray["name"]);
                        $proxyLocation = "@" . $uploadPath . $fileArray["name"] . ";type=" . $fileArray["type"];
                        $postData = array($key => $proxyLocation);
                        $filePost = true;
                    }
                }
                foreach ($_POST as $key => $value) {
                    if (!is_array($value)) {
                        $postData[$key] = $value;
                    } else {
                        $postData[$key] = serialize($value);
                    }
                }
                if (!$filePost) {
                    $postString = "";
                    $firstLoop = true;
                    foreach ($postData as $key => $value) {
                        $parameterItem = urlencode($key) . "=" . urlencode($value);
                        if ($firstLoop) {
                            $postString .= $parameterItem;
                        } else {
                            $postString .= "&" . $parameterItem;
                        }
                        $firstLoop = false;
                    }
                    $postData = $postString;
                }
                $this->sendPost = $postData;
                curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
            }
            // 處理 URL 多重 ? 問題
            $translateURL = $this->translateURL(($this->ip) ? $this->ip : $this->host);
            if (substr_count($translateURL, "?") > 1) {
                $firstPos = strpos($translateURL, "?", 0);
                $secondPos = strpos($translateURL, "?", $firstPos + 1);
                $translateURL = substr($translateURL, 0, $secondPos);
            }
            curl_setopt($ch, CURLOPT_URL, $translateURL);
            $proxyHeaders = array(
                "X-Forwarded-For: " . $this->XFF,
                "User-Agent: " . $this->user_agent,
                "Host: " . $this->host
            );
            if (strlen($this->XRequestedWith) > 1) {
                $proxyHeaders[] = "X-Requested-With: " . $this->XRequestedWith;
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $proxyHeaders);
            if ($this->cookie != "") {
                curl_setopt($ch, CURLOPT_COOKIE, $this->cookie);
            }
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
            curl_setopt($ch, CURLOPT_AUTOREFERER, true);
            curl_setopt($ch, CURLOPT_HEADER, true);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            $output = curl_exec($ch);
            $info = curl_getinfo($ch);
            curl_close($ch);
            $this->postConnect($info, $output);
        } else {
            $this->lastModified = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
            $this->IMS = true;
        }
    }

    function postConnect($info, $output) {
        $this->content_type = $info["content_type"];
        $this->http_code = $info['http_code'];
        if (!empty($info['last_modified'])) {
            $this->lastModified = $info['last_modified'];
        }
        $this->resultHeader = substr($output, 0, $info['header_size']);
        $content = substr($output, $info['header_size']);
        if ($this->http_code == '200') {
            $this->content = $content;
        } elseif (($this->http_code == '302' || $this->http_code == '301') && isset($info['redirect_url'])) {
            $redirect_url = str_replace($this->host, $_SERVER['HTTP_HOST'], $info['redirect_url']);
            if (defined('IS_SAE') && IS_SAE) $redirect_url = str_replace('https://fetchurl.sae.sina.com.cn/', '', $info['redirect_url']);
            header("Location: $redirect_url");
            exit;
        } elseif ($this->http_code == '404') {
            header("HTTP/1.1 404 Not Found");
            exit("HTTP/1.1 404 Not Found");
        } elseif ($this->http_code == '500') {
            header('HTTP/1.1 500 Internal Server Error');
            exit("HTTP/1.1 500 Internal Server Error");
        } else {
            exit("HTTP/1.1 " . $this->http_code . " Internal Server Error");
        }
    }

    function output() {
        $currentTimeString = gmdate("D, d M Y H:i:s", time());
        $expiredTime = gmdate("D, d M Y H:i:s", (time() + $this->cacheTime));
        $doOriginalHeaders = true;
        if ($doOriginalHeaders) {
            if ($this->IMS) {
                header("HTTP/1.1 304 Not Modified");
                header("Date: Wed, $currentTimeString GMT");
                header("Last-Modified: $this->lastModified");
                header("Server: $this->version");
            } else {
                header("HTTP/1.1 200 OK");
                header("Date: Wed, $currentTimeString GMT");
                header("Content-Type: " . $this->content_type);
                header("Last-Modified: $this->lastModified");
                header("Cache-Control: max-age=$this->cacheTime");
                header("Expires: $expiredTime GMT");
                header("Server: $this->version");
                preg_match("/Set-Cookie:[^\n]*/i", $this->resultHeader, $result);
                foreach ($result as $i => $value) {
                    header($result[$i]);
                }
                preg_match("/Content-Encoding:[^\n]*/i", $this->resultHeader, $result);
                foreach ($result as $i => $value) {
                    // header($result[$i]);
                }
                preg_match("/Transfer-Encoding:[^\n]*/i", $this->resultHeader, $result);
                foreach ($result as $i => $value) {
                    // header($result[$i]);
                }
                echo($this->content);
            }
        } else {
            $headerString = $this->resultHeader;
            $headerArray = explode("\n", $headerString);
            foreach ($headerArray as $privHeader) {
                header($privHeader);
            }
            if (stristr($headerString, "Transfer-encoding: chunked")) {
                flush();
                ob_flush();
                $i = 0;
                $maxLen = strlen($this->content);
                while ($i < $maxLen) {
                    $endChar = $i + self::chunkSize;
                    if ($endChar >= $maxLen) {
                        $endChar = $maxLen - 1;
                    }
                    $chunk = substr($this->content, $i, $endChar);
                    $this->dump_chunk($chunk);
                    flush();
                    ob_flush();
                    $i = $i + $endChar;
                }
            } else {
                echo($this->content);
            }
        }
    }

    function dump_chunk($chunk) {
        echo sprintf("%x\r\n", strlen($chunk));
        echo $chunk;
        echo "\r\n";
    }

    function getOutsideHeaders() {
        $headers = array();
        foreach ($_SERVER as $name => $value) {
            if (substr($name, 0, 5) == 'HTTP_') {
                $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
                $headers[$name] = $value;
            } elseif ($name == "CONTENT_TYPE") {
                $headers["Content-Type"] = $value;
            } elseif ($name == "CONTENT_LENGTH") {
                $headers["Content-Length"] = $value;
            } elseif (stristr($name, "X-Requested-With")) {
                $headers["X-Requested-With"] = $value;
                $this->XRequestedWith = $value;
            }
        }
        $this->outsideHeaders = $headers;
        return $headers;
    }
}
?>

三、功能特性

  • 支持 GET/POST 請求代理
  • 支持文件上傳(multipart/form-data)及類型識別
  • 支持指定後端服務器 IP(繞過 DNS)
  • 自動處理 Cookie、User-Agent、X-Forwarded-For 等頭部轉發
  • 支持 301/302 重定向自動重寫 Host
  • 支持 SAE (Sina App Engine) 環境適配
  • 支持緩存控制(Last-Modified、ETag、Cache-Control)
  • 支持分塊傳輸編碼輸出

四、相關問答

Q: 如何用 PHP 實現域名重定向代理?
A: 可在入口文件中根據 $_SERVER['HTTP_HOST'] 判斷域名,實例化代理類指向不同後端;或使用 Apache/Nginx 的虛擬主機配置、.htaccess 重寫規則。

Q: PHP 調用另一個類的靜態方法報錯怎麼辦?
A: 確保先 include/require 載入目標類文件;檢查方法訪問修飾符(public/private);靜態方法調用需用 ClassName::methodName(),不能用 $obj->method()。

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