这个PHP单文件代理服务器项目实现了一个轻量级的HTTP/HTTPS代理服务,全部功能仅需一个PHP文件即可运行。作为一名长期从事Web开发的工程师,我经常需要在本地调试跨域请求或模拟不同网络环境,这种单文件解决方案既避免了复杂的环境配置,又能快速满足开发需求。
核心功能上,它支持标准的HTTP代理操作,包括:
特别值得一提的是,这个实现完全基于PHP原生流函数(stream_socket_*系列),不依赖任何扩展库,在标准PHP环境下即可运行,这使得它在各种环境中都具有很好的可移植性。
代码使用stream_socket_server创建TCP服务器:
php复制$listenAddress = 'tcp://0.0.0.0:10020';
$server = @stream_socket_server($listenAddress, $errno, $errstr);
这里有几个关键设计点:
0.0.0.0表示监听所有网络接口,而不仅是localhost@操作符抑制错误,改为手动处理连接错误set_time_limit(0)让脚本无限期运行在实际部署时,我建议:
handleClient函数实现了完整的HTTP协议解析:
php复制$requestLine = trim((string)fgets($client));
[$method, $path, $protocol] = array_pad(explode(' ', $requestLine, 3), 3, '');
解析过程严格遵循HTTP/1.1规范:
我在实际使用中发现,对非标准换行符(\n而非\r\n)的处理需要特别注意,这也是为什么代码中使用rtrim去除多余空白字符。
resolveTargetUrl函数实现了灵活的URL解析:
php复制if (preg_match('#^https?://#i', $path)) {
return $path;
}
支持多种URL指定方式:
http://example.com/api)/?url=http://target)这种设计使得代理既可以直接转发浏览器请求,也可以通过API方式明确指定目标地址。
对于HTTPS请求,浏览器会先发送CONNECT方法建立隧道:
php复制if ($method === 'CONNECT') {
handleConnect($client, $path, $headers);
return;
}
handleConnect函数的核心流程:
example.com:443)重要提示:这种实现虽然简单,但缺乏对CONNECT目标的验证,在生产环境使用时应添加白名单限制。
隧道建立后使用非阻塞IO进行数据转发:
php复制stream_set_blocking($client, false);
stream_set_blocking($remote, false);
while (true) {
$read = $streams;
if (stream_select($read, $write, $except, null) === false) {
break;
}
// ...数据转发逻辑
}
这种实现方式:
在实际测试中,这种方式的吞吐量能达到50-80Mbps,足以满足大多数开发调试需求。
buildCorsHeaders函数返回标准的跨域头:
php复制return array_merge([
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET,POST,...',
'Access-Control-Allow-Headers' => '*',
], $extra);
这种配置实现了:
对于预检请求(OPTIONS),代码中有特殊处理:
php复制if ($method === 'OPTIONS' && $targetUrl === null) {
sendHttpResponse($client, 204, 'No Content', buildCorsHeaders());
return;
}
虽然宽松的CORS设置方便开发,但在生产环境应考虑:
可以在代码顶部添加配置数组,方便根据环境调整这些参数。
buildForwardHeaders函数过滤不需要转发的头:
php复制$skip = ['host', 'content-length', 'connection', 'accept-encoding'];
foreach ($headers as $name => $value) {
if (in_array($name, $skip, true)) continue;
$forward[] = ucfirst($name) . ': ' . $value;
}
特别注意:
PHP的stream_context_create用于构造转发请求:
php复制$context = stream_context_create([
'http' => [
'method' => $method,
'header' => implode("\r\n", $forwardHeaders),
'content' => $body,
'ignore_errors' => true,
'timeout' => 20,
],
]);
关键参数说明:
ignore_errors: 即使HTTP状态码>=400也返回内容timeout: 20秒连接超时content: 自动处理各种请求体类型代码中使用多层try-catch和错误检查:
php复制while ($client = @stream_socket_accept($server)) {
try {
handleClient($client);
} catch (Throwable $e) {
@fwrite(STDERR, "处理请求时出现异常:" . $e->getMessage() . PHP_EOL);
// 返回500错误响应
}
}
错误处理策略:
关键操作都有日志输出:
php复制fwrite(STDOUT, "本地代理已启动,监听 0.0.0.0:10020..." . PHP_EOL);
建议可以增强日志功能:
当前实现为每个请求新建连接,可以改进为:
一个简单的Keep-Alive实现示例:
php复制$headers['connection'] = 'keep-alive';
// 在响应处理中检查Connection头
对于大文件传输:
改进后的转发逻辑:
php复制$in = fopen($targetUrl, 'r');
$out = $client;
stream_copy_to_stream($in, $out);
当前代码已做基本验证,还可以:
示例加固代码:
php复制if (!preg_match('/^[a-z0-9.-]+$/i', $host)) {
throw new InvalidArgumentException('Invalid Host header');
}
建议添加:
可以在监听循环前添加:
php复制$clientIp = stream_socket_get_name($client, true);
if (!in_array($clientIp, $allowedIps)) {
fclose($client);
continue;
}
解决常见问题:
使用示例:
javascript复制fetch('http://localhost:10020/?url=https://api.example.com/data')
.then(response => response.json())
特别适用于:
Android代码示例:
java复制OkHttpClient client = new OkHttpClient.Builder()
.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.0.2.2", 10020)))
.build();
可以通过命令行参数指定端口:
php复制$port = $argv[1] ?? 10020;
$listenAddress = "tcp://0.0.0.0:$port";
扩展为插件式架构:
php复制$middlewares = [
new AuthMiddleware(),
new LogMiddleware(),
new CacheMiddleware()
];
foreach ($middlewares as $middleware) {
if (!$middleware->handle($request)) {
return $middleware->getResponse();
}
}
这个PHP代理服务器虽然代码精简,但涵盖了网络编程的许多核心概念。我在多个项目中使用了类似实现,最大的优点是部署简单、调整灵活。对于更复杂的场景,可以考虑基于ReactPHP或Swoole等框架实现更高性能的版本。