PHP 怎么PHP反向代理

wen PHP项目 1

本文目录导读:

PHP 怎么PHP反向代理

  1. 使用 cURL 实现简单反向代理
  2. 使用 Guzzle HTTP 客户端
  3. 使用 stream_context 简化版
  4. 高级配置 - 支持 WebSocket 的代理
  5. 使用 Nginx + PHP-FPM 配合
  6. 使用建议

在 PHP 中实现反向代理有多种方式,我来详细介绍几种常用方法:

使用 cURL 实现简单反向代理

<?php
class ReverseProxy {
    private $targetHost;
    private $headers;
    public function __construct($targetHost) {
        $this->targetHost = $targetHost;
        $this->headers = $this->getRequestHeaders();
    }
    public function proxy() {
        $url = $this->targetHost . $_SERVER['REQUEST_URI'];
        $ch = curl_init();
        // 设置请求方法
        $method = $_SERVER['REQUEST_METHOD'];
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        // 设置请求头
        $headers = [];
        foreach ($this->headers as $key => $value) {
            if (!in_array($key, ['Host', 'Content-Length'])) {
                $headers[] = "$key: $value";
            }
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        // 处理请求体
        if (in_array($method, ['POST', 'PUT', 'PATCH'])) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents('php://input'));
        }
        // 设置返回为字符串
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_URL, $url);
        // 执行请求
        $response = curl_exec($ch);
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
        curl_close($ch);
        // 设置响应头
        http_response_code($statusCode);
        if ($contentType) {
            header("Content-Type: $contentType");
        }
        // 输出响应体
        echo $response;
    }
    private function getRequestHeaders() {
        $headers = [];
        foreach ($_SERVER as $key => $value) {
            if (substr($key, 0, 5) == 'HTTP_') {
                $headerName = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
                $headers[$headerName] = $value;
            }
        }
        return $headers;
    }
}
// 使用示例
$proxy = new ReverseProxy('https://api.example.com');
$proxy->proxy();
?>

使用 Guzzle HTTP 客户端

<?php
require_once 'vendor/autoload.php';
use GuzzleHttp\Client;
class GuzzleProxy {
    private $client;
    private $targetBase;
    public function __construct($targetBase) {
        $this->targetBase = $targetBase;
        $this->client = new Client([
            'timeout' => 30.0,
            'verify' => false, // 生产环境建议开启
        ]);
    }
    public function forward() {
        $method = $_SERVER['REQUEST_METHOD'];
        $path = $_SERVER['REQUEST_URI'];
        $url = $this->targetBase . $path;
        // 构建请求头
        $headers = [];
        foreach ($_SERVER as $key => $value) {
            if (substr($key, 0, 5) == 'HTTP_') {
                $headerName = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
                $headers[$headerName] = $value;
            }
        }
        // 移除可能引起问题的头
        unset($headers['Host']);
        unset($headers['Content-Length']);
        try {
            $options = [
                'headers' => $headers,
                'http_errors' => false,
            ];
            // 处理请求体
            if (in_array($method, ['POST', 'PUT', 'PATCH'])) {
                $options['body'] = file_get_contents('php://input');
            }
            // 处理GET参数
            if (!empty($_GET)) {
                $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($_GET);
            }
            // 发送请求
            $response = $this->client->request($method, $url, $options);
            // 设置响应头
            foreach ($response->getHeaders() as $name => $values) {
                foreach ($values as $value) {
                    header("$name: $value", false);
                }
            }
            // 输出响应内容
            echo $response->getBody()->getContents();
        } catch (\Exception $e) {
            http_response_code(502);
            echo "Proxy Error: " . $e->getMessage();
        }
    }
}
// 使用示例
$proxy = new GuzzleProxy('https://api.target.com');
$proxy->forward();
?>

使用 stream_context 简化版

<?php
function simpleReverseProxy($targetUrl) {
    // 获取请求路径
    $requestPath = $_SERVER['REQUEST_URI'];
    $fullUrl = $targetUrl . $requestPath;
    // 创建上下文
    $options = [
        'http' => [
            'method' => $_SERVER['REQUEST_METHOD'],
            'header' => "",
            'content' => file_get_contents('php://input'),
            'ignore_errors' => true,
        ],
        'ssl' => [
            'verify_peer' => false,
            'verify_peer_name' => false,
        ]
    ];
    // 提取并设置请求头
    $headers = [];
    foreach ($_SERVER as $key => $value) {
        if (substr($key, 0, 5) == 'HTTP_') {
            $headerName = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
            if (strtolower($headerName) != 'host') {
                $headers[] = "$headerName: $value";
            }
        }
    }
    if (!empty($headers)) {
        $options['http']['header'] = implode("\r\n", $headers);
    }
    // 创建流上下文
    $context = stream_context_create($options);
    // 执行请求
    $response = @file_get_contents($fullUrl, false, $context);
    // 获取响应头
    if (isset($http_response_header)) {
        foreach ($http_response_header as $header) {
            if (strpos($header, 'HTTP/') === 0) {
                $statusCode = explode(' ', $header)[1];
                http_response_code($statusCode);
            } else {
                header($header, false);
            }
        }
    }
    // 输出响应
    echo $response;
}
// 使用示例
simpleReverseProxy('https://api.example.com');
?>

高级配置 - 支持 WebSocket 的代理

<?php
class AdvancedProxy {
    private $config;
    public function __construct($config) {
        $this->config = array_merge([
            'target' => '',
            'timeout' => 30,
            'max_redirects' => 5,
            'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
            'exclude_paths' => [],
            'custom_headers' => [],
            'ssl_verify' => false,
        ], $config);
    }
    public function handle() {
        // 检查路径是否在排除列表中
        $currentPath = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        if (in_array($currentPath, $this->config['exclude_paths'])) {
            return false;
        }
        // 处理 WebSocket 升级
        if (isset($_SERVER['HTTP_UPGRADE']) && strtolower($_SERVER['HTTP_UPGRADE']) == 'websocket') {
            return $this->handleWebSocket();
        }
        // 常规HTTP请求
        return $this->handleHttp();
    }
    private function handleHttp() {
        $method = $_SERVER['REQUEST_METHOD'];
        // 检查请求方法是否允许
        if (!in_array($method, $this->config['allowed_methods'])) {
            http_response_code(405);
            echo "Method Not Allowed";
            return;
        }
        $cookies = $this->extractCookies();
        $targeting = $this->buildTargetUrl();
        $ch = curl_init();
        // 基础配置
        $curlOptions = [
            CURLOPT_URL => $targeting,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => $this->config['max_redirects'],
            CURLOPT_TIMEOUT => $this->config['timeout'],
            CURLOPT_SSL_VERIFYPEER => $this->config['ssl_verify'],
            CURLOPT_CUSTOMREQUEST => $method,
        ];
        // 处理请求体
        if (in_array($method, ['POST', 'PUT', 'PATCH'])) {
            $curlOptions[CURLOPT_POSTFIELDS] = file_get_contents('php://input');
        }
        // 构建请求头
        $headers = $this->buildHeaders();
        if (!empty($headers)) {
            $curlOptions[CURLOPT_HTTPHEADER] = $headers;
        }
        // 设置cookies
        if (!empty($cookies)) {
            $curlOptions[CURLOPT_COOKIE] = $cookies;
        }
        curl_setopt_array($ch, $curlOptions);
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        if ($error) {
            http_response_code(502);
            echo "Proxy Error: " . $error;
            return;
        }
        http_response_code($status);
        echo $response;
    }
    private function handleWebSocket() {
        // WebSocket 代理实现需要更复杂的处理
        // 这里提供基础的框架
        $target = $this->buildTargetUrl();
        $targetParts = parse_url($target);
        $host = $targetParts['host'];
        $port = isset($targetParts['port']) ? $targetParts['port'] : 80;
        $path = isset($targetParts['path']) ? $targetParts['path'] : '/';
        // 建立到目标的 socket 连接
        $remote = fsockopen($host, $port, $errno, $errstr, 30);
        if (!$remote) {
            http_response_code(502);
            echo "WebSocket connection failed";
            return;
        }
        // 转发请求头
        $request = "GET {$path} HTTP/1.1\r\n";
        $request .= "Host: {$host}:{$port}\r\n";
        $request .= "Upgrade: websocket\r\n";
        $request .= "Connection: Upgrade\r\n";
        $request .= "Sec-WebSocket-Key: " . $_SERVER['HTTP_SEC_WEBSOCKET_KEY'] . "\r\n";
        $request .= "Sec-WebSocket-Version: " . $_SERVER['HTTP_SEC_WEBSOCKET_VERSION'] . "\r\n";
        $request .= "\r\n";
        fwrite($remote, $request);
        // 转发响应
        while (!feof($remote)) {
            echo fread($remote, 8192);
            flush();
        }
        fclose($remote);
    }
    private function buildTargetUrl() {
        $scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
        $host = $this->config['target'];
        $uri = $_SERVER['REQUEST_URI'];
        return rtrim($host, '/') . $uri;
    }
    private function extractCookies() {
        $cookies = [];
        foreach ($_COOKIE as $name => $value) {
            $cookies[] = "$name=" . urlencode($value);
        }
        return implode('; ', $cookies);
    }
    private function buildHeaders() {
        $headers = [];
        foreach ($_SERVER as $key => $value) {
            if (substr($key, 0, 5) == 'HTTP_') {
                $headerName = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
                // 跳过特定的头
                if (in_array(strtolower($headerName), ['host', 'content-length', 'connection', 'accept-encoding'])) {
                    continue;
                }
                $headers[] = "$headerName: $value";
            }
        }
        // 添加自定义头部
        foreach ($this->config['custom_headers'] as $name => $value) {
            $headers[] = "$name: $value";
        }
        return $headers;
    }
}
// 使用示例
$config = [
    'target' => 'https://api.example.com',
    'timeout' => 30,
    'ssl_verify' => false,
    'custom_headers' => [
        'X-Proxy-Server' => 'PHP-Proxy',
        'X-Client-IP' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
    ]
];
$proxy = new AdvancedProxy($config);
$proxy->handle();
?>

使用 Nginx + PHP-FPM 配合

虽然不是纯PHP方案,但这是生产环境中最常见的做法:

# nginx 配置示例
location /api/ {
    proxy_pass http://backend-server:8080/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

使用建议

  1. 性能考虑:如果代理流量较大,建议使用 nginx 或 HAProxy 等专门的代理服务器
  2. 安全考虑:确保验证所有输入,防止恶意请求
  3. 错误处理:添加适当的错误处理机制
  4. 日志记录:记录所有代理请求和响应
  5. 缓存策略:根据需求添加适当的缓存机制

选择哪种方式取决于你的具体需求、环境配置和流量规模,对于简单的API转发,cURL方案就足够了;对于高并发场景,推荐使用 nginx 配合 PHP-FPM。

抱歉,评论功能暂时关闭!