ratchetphp / Ratchet

Asynchronous WebSocket server
http://socketo.me
MIT License
6.25k stars 731 forks source link

HTTP/WS over UDS #990

Open ClosetGeek-Git opened 1 year ago

ClosetGeek-Git commented 1 year ago

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);
l-naumann commented 1 year ago

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
     });