├── composer.json ├── nginx.conf ├── README.md └── index.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "jshensh/php-curl-class": "^0.0.18" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server 2 | { 3 | listen 80; 4 | #listen [::]:80; 5 | server_name example.com; 6 | index index.html index.htm index.php default.html default.htm default.php; 7 | root /home/wwwroot/example.com; 8 | 9 | #error_page 404 /404.html; 10 | 11 | # Deny access to PHP files in specific directory 12 | #location ~ /(wp-content|uploads|wp-includes|images)/.*\.php$ { deny all; } 13 | 14 | location / 15 | { 16 | try_files $uri $uri/ /index.php?$query_string; 17 | } 18 | 19 | location ~ [^/]\.php(/|$) 20 | { 21 | try_files $uri $uri/ /index.php; 22 | fastcgi_pass unix:/tmp/php-cgi.sock; 23 | fastcgi_index index.php; 24 | fastcgi_param PHP_VALUE "enable_post_data_reading=0"; 25 | include fastcgi.conf; 26 | } 27 | 28 | location ~ /.well-known { 29 | allow all; 30 | } 31 | 32 | location ~ /\. 33 | { 34 | deny all; 35 | } 36 | 37 | access_log off; 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP 反向代理 2 | ============ 3 | 4 | 基于 [https://github.com/jshensh/php-curl-class](https://github.com/jshensh/php-curl-class) 的一个应用 5 | 6 | ## 需要修改的配置文件 7 | 8 | ### nginx.conf 9 | 10 | 请替换 Line 5 的域名以及 Line 7 的实际路径 11 | 12 | ### index.php 13 | 14 | 请替换 Line 6 的源站访问协议、Line 7 的源站域名以及 Line 8 的当前站点域名,Line 154 起的输出内容替换数组视情况修改 15 | 16 | ## 功能 17 | - [X] 转发用户的 GET 请求 18 | - [X] 转发用户的 POST 请求 19 | - [X] 转发用户的 PUT 请求 20 | - [X] 转发用户的 DELETE 请求 21 | - [X] 转发用户发送的所有 Header(除 Accept-Encoding 和 Host) 22 | - [X] 转发服务器返回的所有 Header(除 Content-Length 和 Content-Encoding) 23 | - [X] 替换服务器返回的内容 24 | 25 | ## 搭建反代(基于 lnmp) 26 | 27 | ```shell 28 | #!/bin/sh 29 | 30 | originProtocol="http" 31 | originSite="baidu.com" 32 | thisSite="example.com" 33 | # 以上三行需要修改 34 | cd /home/wwwroot 35 | git clone https://github.com/jshensh/phpReverseProxy ${thisSite} 36 | rm -rf ${thisSite}/.git/ 37 | cd ${thisSite} 38 | sed -i "s/example.com/${thisSite}/g" ./nginx.conf 39 | mv ./nginx.conf /usr/local/nginx/conf/vhost/${thisSite}.conf 40 | sed -i "s/http/${originProtocol}/g" ./index.php 41 | sed -i "s/example.com/${thisSite}/g" ./index.php 42 | sed -i "s/baidu.com/${originSite}/g" ./index.php 43 | composer install 44 | chown -R www:www ./* 45 | lnmp nginx reload 46 | ``` 47 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | $value) { 13 | if (substr($name, 0, 5) == 'HTTP_') { 14 | $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; 15 | } 16 | } 17 | return $headers; 18 | } 19 | } 20 | 21 | class ReverseProxy 22 | { 23 | private $flags = [ 24 | 'FILENAME_SENT' => false, 25 | 'HEADER_SENT' => false, 26 | 'CURL_DOWNLOAD_FILE' => false, 27 | 'CURL_VALID_RESPONSE' => true 28 | ]; 29 | private $runtimeData = [ 30 | 'headerSize' => -1, 31 | 'outputBuffer' => [ 32 | 'header' => [], 33 | 'body' => '' 34 | ] 35 | ]; 36 | private $config = [ 37 | 'replace' => null, 38 | ]; 39 | 40 | public function curlCallback($ch, $data) 41 | { 42 | $info = curl_getinfo($ch); 43 | 44 | if ($this->runtimeData['headerSize'] < $info['header_size']) { 45 | $this->runtimeData['headerSize'] = $info['header_size']; 46 | 47 | if (!$this->flags['HEADER_SENT']) { 48 | if (strpos(strtolower($data), 'content-disposition: attachment') !== false) { 49 | $this->flags['FILENAME_SENT'] = true; 50 | $this->flags['CURL_DOWNLOAD_FILE'] = true; 51 | } 52 | if (rtrim($data, "\r\n")) { 53 | $this->runtimeData['outputBuffer']['header'][] = rtrim($data, "\r\n"); 54 | } 55 | } 56 | // header 结束 57 | if ($data === "\r\n") { 58 | $this->runtimeData['headerSize'] += 2; 59 | if (!$this->flags['HEADER_SENT'] && $this->flags['CURL_DOWNLOAD_FILE']) { 60 | foreach ($this->runtimeData['outputBuffer']['header'] as $header) { 61 | if (strpos(strtolower($header), 'content-encoding:') !== false) { 62 | continue; 63 | } 64 | if ($this->config['replace']) { 65 | header(str_replace($this->config['replace'][0], $this->config['replace'][1], $header), false); 66 | } else { 67 | header($header, false); 68 | } 69 | } 70 | if (!$this->flags['FILENAME_SENT']) { 71 | header('Content-Disposition: attachment; filename=' . basename($this->config['filePath'])); 72 | } 73 | $this->flags['HEADER_SENT'] = true; 74 | } 75 | } 76 | } else { 77 | // Data 段数据处理 78 | if ($this->flags['CURL_DOWNLOAD_FILE']) { 79 | echo $data; 80 | ob_flush(); 81 | flush(); 82 | } else { 83 | $this->runtimeData['outputBuffer']['body'] .= $data; 84 | } 85 | } 86 | 87 | return strlen($data); 88 | } 89 | 90 | public function __construct($config) 91 | { 92 | $this->config = array_merge($this->config, $config); 93 | 94 | $curlObj = Client::init("{$this->config['originProtocol']}://{$this->config['originSite']}{$_SERVER['REQUEST_URI']}", $_SERVER['REQUEST_METHOD']) 95 | ->setCurlOpt(CURLOPT_ENCODING, '') 96 | ->setHeader('Expect', '') 97 | ->set('timeout', 0) 98 | ->set('reRequest', 1) 99 | ->set('followLocation', 0); 100 | 101 | $headers = getallheaders(); 102 | 103 | foreach ($headers as $key => $value) { 104 | $keyArr = ['accept-encoding', 'host', 'referer', 'cookie', 'user-agent', 'content-length', 'expect']; 105 | if (in_array(strtolower($key), $keyArr)) { 106 | continue; 107 | } 108 | $curlObj = $curlObj->setHeader($key, str_replace($this->config['thisSite'], $this->config['originSite'], $value)); 109 | } 110 | 111 | if (isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER']) { 112 | $curlObj = $curlObj->set('referer', str_replace($this->config['thisSite'], $this->config['originSite'], $_SERVER['HTTP_REFERER'])); 113 | } 114 | 115 | if (isset($_SERVER['HTTP_COOKIE']) && $_SERVER['HTTP_COOKIE']) { 116 | $curlObj = $curlObj->setCookies(str_replace($this->config['thisSite'], $this->config['originSite'], $_SERVER['HTTP_COOKIE'])); 117 | } 118 | 119 | if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT', 'DELETE'])) { 120 | $curlObj = $curlObj->set('postType', 'string') 121 | ->set('postFields', file_get_contents("php://input")); 122 | } 123 | 124 | $res = $curlObj->setCurlOpt(CURLOPT_WRITEFUNCTION, [$this, 'curlCallback'])->exec(); 125 | 126 | if ($res->getStatus()) { 127 | if (!$this->flags['CURL_DOWNLOAD_FILE']) { 128 | if ($this->config['replace']) { 129 | $this->runtimeData['outputBuffer']['body'] = str_replace($this->config['replace'][0], $this->config['replace'][1], $this->runtimeData['outputBuffer']['body']); 130 | } 131 | 132 | foreach ($this->runtimeData['outputBuffer']['header'] as $header) { 133 | if (strpos(strtolower($header), 'content-length:') !== false) { 134 | header('Content-Length: ' . strlen($this->runtimeData['outputBuffer']['body']), false); 135 | continue; 136 | } 137 | if (strpos(strtolower($header), 'content-encoding:') !== false) { 138 | continue; 139 | } 140 | if (strpos(strtolower($header), 'transfer-encoding:') !== false) { 141 | continue; 142 | } 143 | if ($this->config['replace']) { 144 | header(str_replace($this->config['replace'][0], $this->config['replace'][1], $header), false); 145 | } else { 146 | header($header, false); 147 | } 148 | } 149 | 150 | echo $this->runtimeData['outputBuffer']['body']; 151 | } 152 | } else { 153 | throw new \Exception('Curl error (' . $res->getCurlErrNo() . '): ' . $res->getCurlErr(), $res->getCurlErrNo()); 154 | } 155 | } 156 | } 157 | 158 | try { 159 | new ReverseProxy([ 160 | 'replace' => [ 161 | [ 162 | $originSite 163 | ], [ 164 | $thisSite 165 | ] 166 | ], 167 | 'originProtocol' => $originProtocol, 168 | 'originSite' =>$originSite, 169 | 'thisSite' => $thisSite 170 | ]); 171 | } catch (\Exception $e) { 172 | echo '
' . $e->getMessage() . '
'; 173 | } 174 | --------------------------------------------------------------------------------