本文目录导读:

- 使用 cURL 实现简单反向代理
- 使用 Guzzle HTTP 客户端
- 使用 stream_context 简化版
- 高级配置 - 支持 WebSocket 的代理
- 使用 Nginx + PHP-FPM 配合
- 使用建议
在 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;
}
使用建议
- 性能考虑:如果代理流量较大,建议使用 nginx 或 HAProxy 等专门的代理服务器
- 安全考虑:确保验证所有输入,防止恶意请求
- 错误处理:添加适当的错误处理机制
- 日志记录:记录所有代理请求和响应
- 缓存策略:根据需求添加适当的缓存机制
选择哪种方式取决于你的具体需求、环境配置和流量规模,对于简单的API转发,cURL方案就足够了;对于高并发场景,推荐使用 nginx 配合 PHP-FPM。