Ratchet icon indicating copy to clipboard operation
Ratchet copied to clipboard

HTTP/WS over UDS

Open ClosetGeek-Git opened this issue 3 years ago • 1 comments

It's common for Websocket services to operate behind a reverse proxy, such as can be provided by Nginx or Apache. A common use case is to create this proxy connection over Unix Domain sockets rather than TCP because

  1. UDS typically has somewhat better throughput and lower latency, sometimes significantly better.
  2. Each time a new client connects the the forward facing service (Nginx/Apache), the proxy must create a new connection from itself into the Websocket service, effectively doubling the amount of TCP connections on the OS

This is possible in other high-level websocket frameworks such as socket.io and Django, and is also possible in Ratchet and should likely have first class support. For example:

$react_loop = React\EventLoop\Loop::get();

$sock = new React\Socket\UnixServer('/var/www/ratchet_proxy.sock');

$chat_module = new Chat();

$server = new Ratchet\Server\IoServer(
            new Ratchet\Http\HttpServer( 
                new Ratchet\WebSocket\WsServer( $chat_module ) 
            ),
            $sock, $react_loop
         );

$server->run();

Apache conf (ratchet_proxy.sock wouldn't live in /var/www if the code above is started as a deamon): In this example any requests / ws connections on http://<domain>/api will be proxied into ratchet while all others are handled by using mod_php or PHP-FPM depending on configuration.

<VirtualHost *:80>
     ProxyPass /api unix:/var/www/ratchet_proxy.sock|http://192.168.56.103/api
  
     DocumentRoot /var/www/html

     <Directory /var/www/html>
         Options -Indexes +FollowSymLinks
         AllowOverride All
         Require all granted
     </Directory>
 
      ErrorLog ${APACHE_LOG_DIR}/error.log
      CustomLog ${APACHE_LOG_DIR}/access.log combined  
</VirtualHost>

Additionally this is also possible in ratchetphp/Pawl, however the Ratchet\Client\Connector forces a ws/wss uri. UDS works in pawl with a hackish work around (this is just for example purpose).

ws_client.php

<?php

require __DIR__ . '/vendor/autoload.php';

$reactConnector = new \React\Socket\Connector();

$connector = new React\Socket\Connector();

$loop = \React\EventLoop\Loop::get();
$connector = new \Ratchet\Client\Connector($loop, $reactConnector);

$connector('unix:///tmp/ws.sock')
->then(function(\Ratchet\Client\WebSocket $conn) {
    $conn->on('message', function(\Ratchet\RFC6455\Messaging\MessageInterface $msg) use ($conn) {
        echo "Received: {$msg}\n";
        //$conn->close();
    });

    $conn->on('close', function($code = null, $reason = null) {
        echo "Connection closed ({$code} - {$reason})\n";
    });

    $conn->send('Hello World!');
}, function(\Exception $e) use ($loop) {
    echo "Could not connect: {$e->getMessage()}\n";
    $loop->stop();
});

hack: in ratchet/pawl/src/Connector.php replace lines 38 - 53 the following (note the spoofed uri in generateRequest() ):

        if(str_starts_with($url, 'unix'))
        {
            $uriString = $url;
            $connector = $this->_connector;
            $request = $this->generateRequest('ws://localhost', $subProtocols, $headers);
        
        }else
        {         
            try {
                $request = $this->generateRequest($url, $subProtocols, $headers);
                $uri = $request->getUri();
            } catch (\Exception $e) {
                return new RejectedPromise($e);
            }


            $secure = 'wss' === substr($url, 0, 3);
            $connector = $this->_connector;

            $port = $uri->getPort() ?: ($secure ? 443 : 80);

            $scheme = $secure ? 'tls' : 'tcp';

            $uriString = $scheme . '://' . $uri->getHost() . ':' . $port;
        }

        $connecting = $connector->connect($uriString);

ClosetGeek-Git avatar Jan 01 '23 19:01 ClosetGeek-Git

You can use \React\Socket\FixedUriConnector to use UDS in pawl:

$loop = \React\EventLoop\Loop::get();

$connector = new \Ratchet\Client\Connector(
     $loop,
     new \React\Socket\FixedUriConnector('unix:///tmp/my-ws.sock', new \React\Socket\UnixConnector($loop))
 );

 $connector('ws://127.0.0.1/my-path')
     ->then(function (\Ratchet\Client\WebSocket $conn) {
         // connection established
     });

l-naumann avatar Jan 04 '23 10:01 l-naumann