diff --git a/app/Client/ProxyManager.php b/app/Client/ProxyManager.php index 4519674..0e8b0af 100644 --- a/app/Client/ProxyManager.php +++ b/app/Client/ProxyManager.php @@ -56,20 +56,6 @@ class ProxyManager }); } - private function parseResponse(string $response) - { - try { - return gPsr\parse_response($response); - } catch (\Throwable $e) { - return null; - } - } - - private function parseRequest($data) - { - return gPsr\parse_request($data); - } - protected function getContentLength($proxyConnection): ?int { $request = parse_request($proxyConnection->buffer); diff --git a/app/Server/Connections/Connection.php b/app/Server/Connections/Connection.php index a295e41..55b210d 100644 --- a/app/Server/Connections/Connection.php +++ b/app/Server/Connections/Connection.php @@ -2,8 +2,10 @@ namespace App\Server\Connections; +use GuzzleHttp\Psr7\Request; use Illuminate\Support\Str; use Ratchet\ConnectionInterface; +use React\Stream\Util; class Connection { @@ -22,28 +24,23 @@ class Connection $this->client_id = $clientId; } - public function setProxy(ConnectionInterface $proxy) + public function registerProxy($requestId) { - $this->proxies[] = $proxy; + $this->socket->send(json_encode([ + 'event' => 'createProxy', + 'request_id' => $requestId, + 'client_id' => $this->client_id, + ]) . "||"); } - public function getProxy(): ?ConnectionInterface + public function pipeRequestThroughProxy(HttpRequestConnection $httpConnection, string $requestId, Request $request) { - return array_pop($this->proxies); - } + $this->registerProxy($requestId); - public function rewriteHostInformation($serverHost, $port, $requestId, string $data) - { - $appName = config('app.name'); - $appVersion = config('app.version'); + $this->socket->getConnection()->once('proxy_ready_' . $requestId, function (IoConnection $proxy) use ($request, $requestId, $httpConnection) { + Util::pipe($proxy->getConnection(), $httpConnection->getConnection()); - $originalHost = "{$this->subdomain}.{$serverHost}:{$port}"; - - $data = preg_replace('/Host: '.$this->subdomain.'.'.$serverHost.'(.*)\r\n/', "Host: {$this->host}\r\n" . - "X-Exposed-By: {$appName} {$appVersion}\r\n" . - "X-Expose-Request-ID: {$requestId}\r\n" . - "X-Original-Host: {$originalHost}\r\n", $data); - - return $data; + $proxy->send(\GuzzleHttp\Psr7\str($request)); + }); } } diff --git a/app/Server/Connections/HttpRequestConnection.php b/app/Server/Connections/HttpRequestConnection.php new file mode 100644 index 0000000..d166cb3 --- /dev/null +++ b/app/Server/Connections/HttpRequestConnection.php @@ -0,0 +1,76 @@ +connection = $connection; + + if (! isset($this->connection->buffer)) { + $this->connection->buffer = ''; + } + + $this->connection->buffer .= $message; + } + + public function getRequest(): Request + { + return parse_request($this->connection->buffer); + } + + protected function getContentLength(): ?int + { + return Arr::first($this->getRequest()->getHeader('Content-Length')); + } + + public function hasBufferedAllData() + { + return is_null($this->getContentLength()) || strlen(Str::after($this->connection->buffer, "\r\n\r\n")) === $this->getContentLength(); + } + + public function getConnection() + { + return $this->connection->getConnection(); + } + + public function __get($key) + { + return $this->connection->$key; + } + + public function __set($key, $value) + { + return $this->connection->$key = $value; + } + + public function __unset($key) + { + unset($this->connection->$key); + } + + public function send($data) + { + return $this->connection->send($data); + } + + public function close() + { + return $this->connection->close(); + } +} diff --git a/app/Server/Connections/IoConnection.php b/app/Server/Connections/IoConnection.php index bea1978..7518932 100644 --- a/app/Server/Connections/IoConnection.php +++ b/app/Server/Connections/IoConnection.php @@ -11,7 +11,6 @@ class IoConnection implements ConnectionInterface { */ protected $conn; - /** * @param \React\Socket\ConnectionInterface $conn */ diff --git a/app/Server/Expose.php b/app/Server/Expose.php index a134bf1..a6d178b 100644 --- a/app/Server/Expose.php +++ b/app/Server/Expose.php @@ -3,6 +3,7 @@ namespace App\Server; use App\Server\Connections\ConnectionManager; +use App\Server\Connections\HttpRequestConnection; use App\Server\Messages\ControlMessage; use App\Server\Messages\MessageFactory; use App\Server\Messages\TunnelMessage; @@ -42,12 +43,7 @@ class Expose implements MessageComponentInterface $message = new ControlMessage($payload, $connection, $this->connectionManager); $message->respond(); } else { - if (! isset($connection->buffer)) { - $connection->buffer = ''; - } - $connection->buffer .= $message; - - $message = new TunnelMessage($connection->buffer, $connection, $this->connectionManager); + $message = new TunnelMessage(HttpRequestConnection::wrap($connection, $message), $this->connectionManager); $message->respond(); } } diff --git a/app/Server/Messages/ControlMessage.php b/app/Server/Messages/ControlMessage.php index 392ce9f..c655bd2 100644 --- a/app/Server/Messages/ControlMessage.php +++ b/app/Server/Messages/ControlMessage.php @@ -62,7 +62,5 @@ class ControlMessage implements Message $connectionInfo->socket->getConnection()->emit('proxy_ready_'.$data->request_id, [ $connection, ]); - - $connectionInfo->setProxy($connection); } } diff --git a/app/Server/Messages/RequestModifiers/ModifyHeaders.php b/app/Server/Messages/RequestModifiers/ModifyHeaders.php new file mode 100644 index 0000000..09f5f8b --- /dev/null +++ b/app/Server/Messages/RequestModifiers/ModifyHeaders.php @@ -0,0 +1,25 @@ + [ + 'Host' => $clientConnection->host, + 'X-Expose-Request-ID' => $requestId, + 'X-Exposed-By' => config('app.name') . ' '. config('app.version'), + 'X-Original-Host' => "{$clientConnection->subdomain}.{$connectionManager->host()}:{$connectionManager->port()}", + ] + ]); + + return $request; + } +} diff --git a/app/Server/Messages/RequestModifiers/RequestModifier.php b/app/Server/Messages/RequestModifiers/RequestModifier.php new file mode 100644 index 0000000..262f778 --- /dev/null +++ b/app/Server/Messages/RequestModifiers/RequestModifier.php @@ -0,0 +1,12 @@ +payload = $payload; + protected $requestModifiers = [ + ModifyHeaders::class, + ]; + public function __construct(HttpRequestConnection $connection, ConnectionManager $connectionManager) + { $this->connection = $connection; $this->connectionManager = $connectionManager; @@ -35,7 +37,7 @@ class TunnelMessage implements Message public function respond() { - if ($this->hasBufferedAllData()) { + if ($this->connection->hasBufferedAllData()) { $clientConnection = $this->connectionManager->findConnectionForSubdomain($this->detectSubdomain()); if (is_null($clientConnection)) { @@ -48,52 +50,32 @@ class TunnelMessage implements Message } } - protected function getContentLength(): ?int - { - $request = parse_request($this->connection->buffer); - - return Arr::first($request->getHeader('Content-Length')); - } - protected function detectSubdomain(): ?string { - $subdomain = ''; + $host = $this->connection->getRequest()->getHeader('Host')[0]; - $headers = collect(explode("\r\n", $this->connection->buffer))->map(function ($header) use (&$subdomain) { - $headerData = explode(':', $header); - if ($headerData[0] === 'Host') { - $domainParts = explode('.', $headerData[1]); - $subdomain = trim($domainParts[0]); - } - }); + $domainParts = explode('.', $host); - return $subdomain; + return trim($domainParts[0]); } - private function copyDataToClient(Connection $clientConnection) + protected function passRequestThroughModifiers(string $requestId, Request $request, Connection $clientConnection): Request + { + foreach ($this->requestModifiers as $requestModifier) { + $request = app($requestModifier)->modify($request, $requestId, $clientConnection, $this->connectionManager); + } + + return $request; + } + + protected function copyDataToClient(Connection $clientConnection) { $requestId = uniqid(); - $data = $clientConnection->rewriteHostInformation($this->connectionManager->host(), $this->connectionManager->port(), $requestId, $this->connection->buffer); + $request = $this->passRequestThroughModifiers($requestId, $this->connection->getRequest(), $clientConnection); - // Ask client to create a new proxy - $clientConnection->socket->send(json_encode([ - 'event' => 'createProxy', - 'request_id' => $requestId, - 'client_id' => $clientConnection->client_id, - ]) . "||"); - - $clientConnection->socket->getConnection()->once('proxy_ready_' . $requestId, function (IoConnection $proxy) use ($data, $requestId) { - Util::pipe($proxy->getConnection(), $this->connection->getConnection()); - - $proxy->send($data); - }); + $clientConnection->pipeRequestThroughProxy($this->connection, $requestId, $request); unset($this->connection->buffer); } - - protected function hasBufferedAllData() - { - return is_null($this->getContentLength()) || strlen(Str::after($this->connection->buffer, "\r\n\r\n")) === $this->getContentLength(); - } }