sirn-se / websocket-php

[php-lib] WebSocket client and server in PHP
Other
117 stars 18 forks source link

504 Gateway Timeout #73

Closed Orzubek-Rakhimov closed 1 month ago

Orzubek-Rakhimov commented 2 months ago

I am using websocket-php for Client and cboden/ratchet for Server

Server side working nomrally but when i add this code to Client side index.php it causes to 504 Gateway Timeout (nginx). I've been struggling with this for two days, please help!

for Client side code :

<?php

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

$client = new WebSocket\Client("ws://localhost:9001");
$client
    // Add standard middlewares
    ->addMiddleware(new WebSocket\Middleware\CloseHandler())
    ->addMiddleware(new WebSocket\Middleware\PingResponder())
;

// Send a message
$client->text("Hello WebSocket.org!");

// Read response (this is blocking)
$message = $client->receive();
echo "Got message: {$message->getContent()} \n";

// Close connection
$client->close();

?>

for Server side code

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

use Ratchet\Http\HttpServer;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;

class Chat implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn) {
        // Store the new connection to send messages to later
        $this->clients->attach($conn);

        echo "New connection! ({$conn->resourceId})\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {

        foreach ($this->clients as $client) {
            if ($from !== $client) {
                // The sender is not the receiver, send to each client connected
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn) {
        // The connection is closed, remove it, as we can no longer send it messages
        $this->clients->detach($conn);
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {

        $conn->close();
    }
}

try {
    $server = IoServer::factory(
        new HttpServer(
            new WsServer(
                new Chat()
            )
        ),
        9001
    );

    $server->run();
}catch (Exception $e){
    echo $e->getMessage();
}
sirn-se commented 2 months ago

Hi @Orzubek-Rakhimov

The "504 Gateway Timeout" implies an issue on server side. It typically occurs when request is initially handled by a proxy server, but the proxy doesn't get an answer from the actual server it delegates the request to.

Note that not all proxies support websocket requests. Nginx do support websocket, but as I understand (haven't used this setup myself) it need som extra configuration. To start with, some headers need to be included when forwarding as the handshake relies on these.

I'm not familiar with Rachet or setting up Nginx as a websocket proxy, but maybe someone else can provide some input.

Orzubek-Rakhimov commented 2 months ago

It's all due to blocking code on the client side like


$message = $client->receive();

# or 

$client->start()

i checked source code and find loops that didnt end and it dont let request end and triggered 504.


while ($this->running) {
            try {
                // Get streams with readable content
                $readables = $this->streams->waitRead($this->timeout);
                foreach ($readables as $key => $readable) {
                    try {
                        // Read from connection
                        if ($message = $this->connection->pullMessage()) {
                            $this->dispatch($message->getOpcode(), [$this, $this->connection, $message]);
                        }
                    } catch (MessageLevelInterface $e) {
                        // Error, but keep connection open
                        $this->logger->error("[client] {$e->getMessage()}");
                        $this->dispatch('error', [$this, $this->connection, $e]);
                    } catch (ConnectionLevelInterface $e) {
                        // Error, disconnect connection
                        $this->disconnect();
                        $this->logger->error("[client] {$e->getMessage()}");
                        $this->dispatch('error', [$this, $this->connection, $e]);
                    }
                }
                if (!$this->connection->isConnected()) {
                    $this->running = false;
                }
                $this->connection->tick();
                $this->dispatch('tick', [$this]);
            } catch (Exception $e) {
                $this->disconnect();
                $this->running = false;

                // Low-level error
                $this->logger->error("[client] {$e->getMessage()}");
                $this->dispatch('error', [$this, null, $e]);
            } catch (Throwable $e) {
                $this->disconnect();
                $this->running = false;

                // Crash it
                $this->logger->error("[client] {$e->getMessage()}");
                $this->dispatch('error', [$this, null, $e]);
                throw $e;
            }

When I remove them from code all things work pretty good. Please let me i am wrong or not. Sorry for my english it is not my first language.

edit: without calling $client->start() messages dont send. BUt when i call start() it cause to 502

sirn-se commented 2 months ago

The start() is for continuous subscription of messages sent by server, so it should be in a loop. To exit the loop, use close() or stop() depending on what you need.

The receive() is to read a single message, but then you need to know if and how the server will respond. If it "hangs", it's because the server haven't responded - client is waiting for something to read.

The client do not trigger 504, that error is triggered elsewhere in your setup. Most likely a proxy.