├── .gitignore ├── composer.json ├── bin ├── http-proxy.php ├── co-tcp-proxy.php └── tcp-proxy.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.lock 4 | .php-cs-fixer.cache -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require-dev": { 3 | "friendsofphp/php-cs-fixer": "^3.89" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /bin/http-proxy.php: -------------------------------------------------------------------------------- 1 | set(array('keep_alive' => true)); 22 | self::$upstreams[$fd] = $client; 23 | } 24 | return self::$upstreams[$fd]; 25 | } 26 | 27 | public static function unsetClient($fd): void 28 | { 29 | unset(self::$upstreams[$fd]); 30 | } 31 | } 32 | 33 | $serv = new Server('127.0.0.1', 9510, SWOOLE_BASE); 34 | 35 | $serv->on('Close', function ($serv, $fd, $reactorId) { 36 | HttpProxyServer::unsetClient($fd); 37 | }); 38 | 39 | $serv->on('Request', function (request $req, response $resp) { 40 | // HTTP does not support concurrency. When processing a request and there is no response, the client will not send a new request 41 | $client = HttpProxyServer::getClient($req->fd); 42 | if ($req->server['request_method'] == 'GET') { 43 | $rs = $client->get($req->server['request_uri']); 44 | } elseif ($req->server['request_method'] == 'POST') { 45 | $rs = $client->post($req->server['request_uri'], $req->getContent()); 46 | } else { 47 | $resp->status(405); 48 | $resp->end("Method Not Allow"); 49 | return; 50 | } 51 | if ($rs) { 52 | $resp->end($client->body); 53 | } else { 54 | $resp->status(502); 55 | $resp->end("Bad Gateway"); 56 | } 57 | }); 58 | 59 | HttpProxyServer::$server = $serv; 60 | $serv->start(); 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proxy-server 2 | Full asynchronous proxy server can support over a large number of concurrent. 3 | 4 | Install 5 | ===== 6 | ```shell 7 | pecl install swoole 8 | ``` 9 | 10 | Run 11 | ==== 12 | ```shell 13 | php tcp-proxy.php 14 | php http-proxy.php 15 | ``` 16 | 17 | Test 18 | === 19 | * Ubuntu 14.04 + inter I5 4 core + 8G Memory 20 | 21 | ```shell 22 | ab -c 1000 -n 1000000 -k http://127.0.0.1:9509/ 23 | ab -c 1000 -n 1000000 -k http://127.0.0.1:9509/ 24 | ``` 25 | 26 | ```shell 27 | htf@htf-All-Series:~/workspace/proj/proxy-server$ ab -c 1000 -n 1000000 -k http://127.0.0.1:9509/ 28 | This is ApacheBench, Version 2.3 <$Revision: 1528965 $> 29 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 30 | Licensed to The Apache Software Foundation, http://www.apache.org/ 31 | 32 | Benchmarking 127.0.0.1 (be patient) 33 | Completed 100000 requests 34 | Completed 200000 requests 35 | Completed 300000 requests 36 | Completed 400000 requests 37 | Completed 500000 requests 38 | Completed 600000 requests 39 | Completed 700000 requests 40 | Completed 800000 requests 41 | Completed 900000 requests 42 | Completed 1000000 requests 43 | Finished 1000000 requests 44 | 45 | 46 | Server Software: nginx/1.4.6 47 | Server Hostname: 127.0.0.1 48 | Server Port: 9509 49 | 50 | Document Path: / 51 | Document Length: 371 bytes 52 | 53 | Concurrency Level: 1000 54 | Time taken for tests: 11.347 seconds 55 | Complete requests: 1000000 56 | Failed requests: 0 57 | Keep-Alive requests: 990487 58 | Total transferred: 616952435 bytes 59 | HTML transferred: 371000000 bytes 60 | Requests per second: 88131.61 [#/sec] (mean) 61 | Time per request: 11.347 [ms] (mean) 62 | Time per request: 0.011 [ms] (mean, across all concurrent requests) 63 | Transfer rate: 53098.64 [Kbytes/sec] received 64 | 65 | Connection Times (ms) 66 | min mean[+/-sd] median max 67 | Connect: 0 0 0.8 0 26 68 | Processing: 0 11 26.6 8 1047 69 | Waiting: 0 11 26.6 8 1047 70 | Total: 0 11 26.9 8 1055 71 | 72 | Percentage of the requests served within a certain time (ms) 73 | 50% 8 74 | 66% 12 75 | 75% 14 76 | 80% 16 77 | 90% 22 78 | 95% 27 79 | 98% 34 80 | 99% 39 81 | 100% 1055 (longest request) 82 | ``` 83 | -------------------------------------------------------------------------------- /bin/co-tcp-proxy.php: -------------------------------------------------------------------------------- 1 | '127.0.0.1', 'port' => '80'); 15 | 16 | public function run(): void 17 | { 18 | $serv = new Server("127.0.0.1", 9508); 19 | $serv->set(array( 20 | 'worker_num' => $this->workerNum, 21 | //'log_file' => '/tmp/swoole.log', //swoole error log 22 | )); 23 | $serv->on('WorkerStart', array($this, 'onStart')); 24 | $serv->on('Connect', [$this, 'onConnect']); 25 | $serv->on('Receive', array($this, 'onReceive')); 26 | $serv->on('Close', array($this, 'onClose')); 27 | $serv->on('WorkerStop', array($this, 'onShutdown')); 28 | $serv->start(); 29 | } 30 | 31 | public function onStart($serv): void 32 | { 33 | $this->server = $serv; 34 | echo "Server is started, SWOOLE version is [" . SWOOLE_VERSION . "]\n"; 35 | } 36 | 37 | public function onShutdown($serv): void 38 | { 39 | echo "Server: onShutdown\n"; 40 | } 41 | 42 | public function onConnect($serv, $fd): void 43 | { 44 | $channel = new Channel(64); 45 | $this->channels[$fd] = $channel; 46 | 47 | $socket = new Client(SWOOLE_SOCK_TCP); 48 | if (!$socket->connect($this->backendServer['host'], $this->backendServer['port'])) { 49 | trigger_error("bad backend server " . $this->backendServer['host'] . ":" . $this->backendServer['port'] . ', Error: ' . $socket->errCode, E_USER_WARNING); 50 | $serv->close($fd); 51 | return; 52 | } 53 | 54 | // read from backend and send to client 55 | Co\go(function () use ($serv, $socket, $fd) { 56 | while (true) { 57 | $data = $socket->recv(); 58 | if ($data === '' || $data === false) { 59 | break; 60 | } 61 | $serv->send($fd, $data); 62 | } 63 | if ($serv->exist($fd)) { 64 | $serv->close($fd); 65 | } 66 | }); 67 | 68 | // read from client and send to backend 69 | Co\go(function () use ($socket, $channel) { 70 | while (true) { 71 | $data = $channel->pop(); 72 | if ($data === '' || $data === false) { 73 | break; 74 | } 75 | $socket->send($data); 76 | } 77 | }); 78 | 79 | $this->sockets[$fd] = $socket; 80 | } 81 | 82 | public function onClose($serv, $fd, $reactor_id): void 83 | { 84 | if (isset($this->channels[$fd])) { 85 | $channel = $this->channels[$fd]; 86 | $channel->close(); 87 | } 88 | if (isset($this->sockets[$fd])) { 89 | $socket = $this->sockets[$fd]; 90 | $socket->close(); 91 | } 92 | } 93 | 94 | public function onReceive($serv, $fd, $reactor_id, $data): void 95 | { 96 | $channel = $this->channels[$fd]; 97 | $channel->push($data); 98 | } 99 | } 100 | 101 | $serv = new CoProxyServer(); 102 | $serv->run(); 103 | -------------------------------------------------------------------------------- /bin/tcp-proxy.php: -------------------------------------------------------------------------------- 1 | '127.0.0.1', 'port' => '80'); 18 | 19 | public function run(): void 20 | { 21 | $serv = new Server("127.0.0.1", 9509, $this->mode); 22 | $serv->set(array( 23 | 'worker_num' => $this->workerNum, 24 | //'log_file' => '/tmp/swoole.log', //swoole error log 25 | )); 26 | $serv->on('WorkerStart', array($this, 'onStart')); 27 | $serv->on('Connect', [$this, 'onConnect']); 28 | $serv->on('Receive', array($this, 'onReceive')); 29 | $serv->on('Close', array($this, 'onClose')); 30 | $serv->on('WorkerStop', array($this, 'onShutdown')); 31 | $serv->start(); 32 | } 33 | 34 | public function onStart($serv): void 35 | { 36 | $this->serv = $serv; 37 | echo "Server is started, SWOOLE version is [" . SWOOLE_VERSION . "]\n"; 38 | } 39 | 40 | public function onShutdown($serv): void 41 | { 42 | echo "Server: onShutdown\n"; 43 | } 44 | 45 | public function onConnect($serv, $fd): void { 46 | $this->rx_buffer[$fd] = []; 47 | } 48 | 49 | public function onClose($serv, $fd, $reactor_id): void 50 | { 51 | if (isset($this->upstreams[$fd])) { 52 | $socket = $this->upstreams[$fd]; 53 | $this->closing_fds[$socket->sock] = true; 54 | $socket->close(); 55 | unset($this->upstreams[$fd]); 56 | } 57 | unset($this->rx_buffer[$fd]); 58 | } 59 | 60 | protected function getSocket($fd): Client 61 | { 62 | return $this->upstreams[$fd]; 63 | } 64 | 65 | public function onReceive($serv, $fd, $reactor_id, $data): void 66 | { 67 | if (!isset($this->upstreams[$fd])) { 68 | $socket = new Client(SWOOLE_SOCK_TCP); 69 | $this->upstreams[$fd] = $socket; 70 | $this->rx_buffer[$fd][] = $data; 71 | $socket->on('connect', function (Client $socket) use ($fd) { 72 | foreach ($this->rx_buffer[$fd] as $data) { 73 | $socket->send($data); 74 | } 75 | $this->closing_fds[$socket->sock] = false; 76 | }); 77 | 78 | $socket->on('error', function (Client $socket) use ($fd) { 79 | echo "ERROR: connect to backend server failed\n"; 80 | $this->serv->send($fd, "backend server not connected. please try reconnect."); 81 | $this->serv->close($fd); 82 | }); 83 | 84 | $socket->on('close', function (Client $socket) use ($fd) { 85 | if (!$this->closing_fds[$socket->sock]) { 86 | $this->serv->close($fd); 87 | } 88 | unset($this->upstreams[$fd]); 89 | unset($this->rx_buffer[$fd]); 90 | unset($this->closing_fds[$socket->sock]); 91 | }); 92 | 93 | $socket->on('receive', function (Client $socket, $_data) use ($fd) { 94 | $this->serv->send($fd, $_data); 95 | }); 96 | 97 | if (!$socket->connect($this->backendServer['host'], $this->backendServer['port'])) { 98 | echo "ERROR: cannot connect to backend server.\n"; 99 | $this->serv->send($fd, "backend server not connected. please try reconnect."); 100 | $this->serv->close($fd); 101 | } 102 | } else { 103 | $socket = $this->getSocket($fd); 104 | if ($socket->isConnected()) { 105 | $socket->send($data); 106 | } else { 107 | $this->rx_buffer[$fd][] = $data; 108 | } 109 | } 110 | } 111 | } 112 | 113 | $serv = new ProxyServer(); 114 | $serv->run(); 115 | --------------------------------------------------------------------------------