Laravel-websockets: Custom WebsocketHandler not work if APP_DEBUG=false

Created on 6 Dec 2019  路  10Comments  路  Source: beyondcode/laravel-websockets

I created custom websockethandler and its work fine when APP_DEBUG=true in .env
but when i set APP_DEBUG=false its stop working

class CustomSocketHandler extends WebSocketHandler { }

i think that is bad idea if i have to enable APP_DEBUG=true

bug good first issue

Most helpful comment

I thought that was bollocks, so I finally settled for:

WebSocketsRouter::webSocket('/app/{appKey}', \App\WebSocket\WebSocketHandler::class);

Simply overwriting the default app route to force it through my own handler.

All 10 comments

In the documentation the custom handler is a separate class, it does not extends WebSocketHandler.

class MyCustomWebSocketHandler implements MessageComponentInterface { }

It works with APP_DEBUG=false.

WebSocketHandler also implements the MessageComponentInterface. My guess is that @ayuningnareswari is extending that class, just to override the onMessage with custom logic, without having to rewrite the other methods (onOpen, onClose and onError).

Changing the code from extends WebSocketHandler to implements MessageComponentInterface (and copying over the implementations of those methods) yields the same result: The custom handler code is simply bypassed entirely when APP_DEBUG is set to false.

I've been trying to pin-point where it goes wrong, but I don't see any exceptions being logged. Turns out that debugging an app with debugging disabled is quite a hassle :-)

Changing the code from extends WebSocketHandler to implements MessageComponentInterface yields the same result: The custom handler code is simply bypassed entirely when APP_DEBUG is set to false.

This happens because of how the WebsocketsLogger singleton is applied.
I'm using the code from here as a workaround.

Yeah, I came across that one, and I think I tried that already, and it did not make any difference. But I've had a good night's sleep, so I'm gonna try again this morning with fresh eyes. I'll let you know.

Applying that workaround didn't yield any difference for me. Since the code was doing an array search of the action in the customRoutes, I figured: why not dump this stuff in the log and see what happens.

    protected function createWebSocketsServer(string $action): WsServer
    {
        $app = app($action);

        \Log::info("action: $action");
        \Log::info("custom routes: ");
        \Log::info($this->customRoutes->all());
        \Log::info("websockets logger is enabled: ".WebsocketsLogger::isEnabled());

        if (WebsocketsLogger::isEnabled()) {
            $app = WebsocketsLogger::decorate($app);
        }

        return new WsServer($app);
    }

With APP_DEBUG=true, this yields:

[2020-03-03 09:02:37] production.INFO: action: BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler  
[2020-03-03 09:02:37] production.INFO: custom routes:   
[2020-03-03 09:02:37] production.INFO: array (
  '/ws' => 'App\\WebSocket\\WebSocketHandler',
)  
[2020-03-03 09:02:37] production.INFO: websockets logger is enabled: 1  

[2020-03-03 09:02:37] production.INFO: action: App\WebSocket\WebSocketHandler  
[2020-03-03 09:02:37] production.INFO: custom routes:   
[2020-03-03 09:02:37] production.INFO: array (
  '/ws' => 'App\\WebSocket\\WebSocketHandler',
)  
[2020-03-03 09:02:37] production.INFO: websockets logger is enabled: 1  

With APP_DEBUG=false, this yields:

[2020-03-03 09:03:19] production.INFO: action: BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler  
[2020-03-03 09:03:19] production.INFO: custom routes:   
[2020-03-03 09:03:19] production.INFO: array (
  '/ws' => 'App\\WebSocket\\WebSocketHandler',
)  
[2020-03-03 09:03:19] production.INFO: websockets logger is enabled:   

[2020-03-03 09:03:19] production.INFO: action: App\WebSocket\WebSocketHandler  
[2020-03-03 09:03:19] production.INFO: custom routes:   
[2020-03-03 09:03:19] production.INFO: array (
  '/ws' => 'App\\WebSocket\\WebSocketHandler',
)  
[2020-03-03 09:03:19] production.INFO: websockets logger is enabled:   

So in order for my custom handler to work, I need to basically do this:

    protected function createWebSocketsServer(string $action): WsServer
    {
        $app = app($action);

        $app = WebsocketsLogger::decorate($app);

        return new WsServer($app);
    }

If I dig a bit further, I found that the $app that is resolved based on the $action is something different than the $app which is decorated by means of the WebsocketsLogger.

$app = app($action):

BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler {#2933
  #channelManager: BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManagers\ArrayChannelManager {#2935
    #appId: null
    #channels: []
  }
}

$app = WebsocketsLogger::decorate($app):

BeyondCode\LaravelWebSockets\Server\Logger\WebsocketsLogger {#2937
  #app: BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler {#2933
    #channelManager: BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManagers\ArrayChannelManager {#2935
      #appId: null
      #channels: []
    }
  }
[...]

My custom route:

$app = app($action):

App\WebSocket\WebSocketHandler {#2963
  #channelManager: BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManagers\ArrayChannelManager {#2932
    #appId: null
    #channels: []
  }
}

$app = WebsocketsLogger::decorate($app):

BeyondCode\LaravelWebSockets\Server\Logger\WebsocketsLogger {#2936
  #app: App\WebSocket\WebSocketHandler {#2963
    #channelManager: BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManagers\ArrayChannelManager {#2932
      #appId: null
      #channels: []
    }
  }
[...]

In my case, whenever the $app resolved from the $action is passed to Ratchet's WsServer, stuff stops working. If that WebsocketLogger decorated version is passed, everything works peachy.

As far as I can tell, the only thing that WebsocketLogger does is access some stuff in order to get some data to output to the console, but ultimately they defer to the methods on the #app.

Really frustrating to debug like this.

A colleague of mine ultimately resorted in creating a CustomRouter that overrides echo() to use our own WebSocketHandler::class for the default GET /app/{appKey} route. This also required him to create a separate Console Command to start our custom websocket server.

I thought that was bollocks, so I finally settled for:

WebSocketsRouter::webSocket('/app/{appKey}', \App\WebSocket\WebSocketHandler::class);

Simply overwriting the default app route to force it through my own handler.

My Custom Handler still checks if it's pusher:protocol messages or our own events that need our own logic, so basically we still have Pusher compatibility as well as custom logic. Which I believe was another question #309

@axit-joost Thanks for your update

Workaround by @matheusb-comp works (at least in my case). I needed to add a route to / for HAProxy healthchecks.

  1. Override websockets router:
class Router extends \BeyondCode\LaravelWebSockets\Server\Router
{
    /**
     * @param string $action
     * @return WsServer
     */
    protected function createWebSocketsServer(string $action): WsServer
    {
        $app = app($action);

        if (WebsocketsLogger::isEnabled() && !$this->isCustomAction($action)) {
            $app = WebsocketsLogger::decorate($app);
        }

        return new WsServer($app);
    }

    /**
     * @param string $action
     * @return bool
     */
    private function isCustomAction(string $action): bool
    {
        return false !== array_search($action, $this->customRoutes->all());
    }
}
  1. Update router binding (for example in AppServiceProvider):
$this->app->singleton('websockets.router', function () {
    return new Router();
});
  1. Register your route and handler
WebSocketsRouter::webSocket('/', IndexHandler::class);
Was this page helpful?
0 / 5 - 0 ratings

Related issues

henzeb picture henzeb  路  4Comments

darklight9811 picture darklight9811  路  4Comments

vesper8 picture vesper8  路  3Comments

Arslan1122 picture Arslan1122  路  4Comments

ElegantSoft picture ElegantSoft  路  3Comments