HTTP/WS over UDS
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
- UDS typically has somewhat better throughput and lower latency, sometimes significantly better.
- 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);
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
});