laravel / reverb

Laravel Reverb provides a real-time WebSocket communication backend for Laravel applications.
https://reverb.laravel.com
MIT License
1.05k stars 76 forks source link

PusherBroadcaster 404 Error on Production Server #112

Closed dysTOS closed 5 months ago

dysTOS commented 5 months ago

Reverb Version

1.x

Laravel Version

11.0.7

PHP Version

8.2

Description

First, I already have a fully working reverb websocket connection when developing locally - Private-Channels, Authentication, Notifications, SSL - everything works like a charm. My frontend is a seperate angular-application running on a different subdomain.

On my production server the websocket connection is also established as it should be. BUT when firing a notification that should be broadcasted via reverb, the queued job fails with the following error:

Illuminate\Broadcasting\BroadcastException: Pusher error: <!DOCTYPE html>
<html lang="en">
    // some html that says 404 not found
</html>

. in /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php:164
Stack trace:
#0 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastEvent.php(92): Illuminate\Broadcasting\Broadcasters\PusherBroadcaster->broadcast()
#1 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Illuminate\Broadcasting\BroadcastEvent->handle()
#2 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Container/Util.php(41): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}()
#3 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\Container\Util::unwrapIfClosure()
#4 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\Container\BoundMethod::callBoundMethod()
#5 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Container/Container.php(662): Illuminate\Container\BoundMethod::call()
#6 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(128): Illuminate\Container\Container->call()
#7 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\Bus\Dispatcher->Illuminate\Bus\{closure}()
#8 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}()
#9 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(132): Illuminate\Pipeline\Pipeline->then()
#10 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(123): Illuminate\Bus\Dispatcher->dispatchNow()
#11 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\Queue\CallQueuedHandler->Illuminate\Queue\{closure}()
#12 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}()
#13 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(122): Illuminate\Pipeline\Pipeline->then()
#14 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(70): Illuminate\Queue\CallQueuedHandler->dispatchThroughMiddleware()
#15 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(102): Illuminate\Queue\CallQueuedHandler->call()
#16 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(439): Illuminate\Queue\Jobs\Job->fire()
#17 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(389): Illuminate\Queue\Worker->process()
#18 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(176): Illuminate\Queue\Worker->runJob()
#19 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(139): Illuminate\Queue\Worker->daemon()
#20 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(122): Illuminate\Queue\Console\WorkCommand->runWorker()
#21 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): Illuminate\Queue\Console\WorkCommand->handle()
#22 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Container/Util.php(41): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}()
#23 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(93): Illuminate\Container\Util::unwrapIfClosure()
#24 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(35): Illuminate\Container\BoundMethod::callBoundMethod()
#25 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Container/Container.php(662): Illuminate\Container\BoundMethod::call()
#26 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Console/Command.php(212): Illuminate\Container\Container->call()
#27 /var/www/vhosts/example.com/api.example.com/vendor/symfony/console/Command/Command.php(279): Illuminate\Console\Command->execute()
#28 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Console/Command.php(181): Symfony\Component\Console\Command\Command->run()
#29 /var/www/vhosts/example.com/api.example.com/vendor/symfony/console/Application.php(1049): Illuminate\Console\Command->run()
#30 /var/www/vhosts/example.com/api.example.com/vendor/symfony/console/Application.php(318): Symfony\Component\Console\Application->doRunCommand()
#31 /var/www/vhosts/example.com/api.example.com/vendor/symfony/console/Application.php(169): Symfony\Component\Console\Application->doRun()
#32 /var/www/vhosts/example.com/api.example.com/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(196): Symfony\Component\Console\Application->run()
#33 /var/www/vhosts/example.com/api.example.com/artisan(35): Illuminate\Foundation\Console\Kernel->handle()
#34 {main}

I already thought of a problem in the .env file - but I could not find a solution so far...

REVERB_APP_ID=app_id
REVERB_APP_KEY=app_key
REVERB_APP_SECRET=app_secret
REVERB_HOST=subdomain-of-my-laravel-backend.example.com // also tried with localhost and frontend-subdomain
REVERB_PORT=443 // this is also the default value in the config/reverb.php 
REVERB_SCHEME=https

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

Steps To Reproduce

sorry for missing out that

joedixon commented 5 months ago

Hi @dysTOS, what does your Nginx (or equivalent) configuration look like?

dysTOS commented 5 months ago

Thanks for your reply @joedixon ! I was able to get this partially solved in the meanwhile.

I have upgraded laravel/framework to 11.0.8 and did some changes to my webserver-configuration and .env variables, but went back to settings i already had before in the end, so unfortunately I am not sure what solved this issue exactly.

Here are my current configurations and relevant settings:

Backend: api.example.com (running in a Plesk Environment with Apache + Nginx as reverse proxy) Frontend: app.example.com (Angular SPA)

Frontend (Typescript)

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

// invoked on init
(window as any).Pusher = Pusher;
    this._echo = new Echo({
      broadcaster: 'reverb',
      key: 'appkey',
      wsHost: 'api.example.com',
      wsPort: 443,
      wssPort: 443,
      forceTLS: true,
      enabledTransports: ['ws', 'wss'],
      authorizer: (channel, options) => {
        return {
          authorize: (socketId, callback) => {
            firstValueFrom(
              this.httpClient.post('https://api.example.com/api/broadcasting/auth', {
                socket_id: socketId,
                channel_name: channel.name,
              })
            )
              .then((response) => {
                callback(false, response);
              })
              .catch((error) => {
                callback(true, error);
              });
          },
        };
      },
    });

// invoked after ws-connection is established to subscribe to the private-channel
this._echo.private('App.Models.User.' + this.myUserService.getUserId()).notification((e) => {
          doMyStuff(e);
});

Laravel .env

APP_NAME=test
APP_ENV=production
APP_KEY=key
APP_DEBUG=true
APP_URL=https://api.example.com

LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=production

BROADCAST_DRIVER=pusher
CACHE_DRIVER=file
FILESYSTEM_DRIVER=local
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

REVERB_APP_ID='id'
REVERB_APP_KEY='key'
REVERB_APP_SECRET='secret'
REVERB_HOST=api.example.com
REVERB_PORT=8080
REVERB_SCHEME=http

#NOT NEEDED AS FRONTEND IS SEPERATE APP
#VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
#VITE_REVERB_HOST=api.example.com
#VITE_REVERB_PORT=80
#VITE_REVERB_SCHEME=http

Laravel routes/channels.php

Broadcast::channel('App.Models.User.{userId}', function ($user, $userId) {
    return $user->id == $userId;
});

Laravel Providers/BroadcastServiceProvider.php

class BroadcastServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        // needed this because I use laravel/sanctum for authentication
        Broadcast::routes(['prefix' => 'api', 'middleware' => ['auth:sanctum']]);

        require base_path('routes/channels.php');
    }
}

PLESK additional Nginx directives

location /app/ {
    proxy_pass http://0.0.0.0:8080;
    proxy_set_header        Upgrade $http_upgrade;
    proxy_set_header        Connection "upgrade";
    proxy_http_version       1.1;
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Tried also with additional 'location /apps/', 'location /ws/', 'location /wss/' and same content, but it seems that /app/ is sufficient.

BUT there is still one issue - from 10 sent notifications only an average of 2 to 3 messages are received by my frontend - found no log-entries or failed jobs.

dysTOS commented 5 months ago

@joedixon please forget my last sentence - EVERY message is sent but it took up to 10 minutes until the last message was received by my frontend application. I will have a look on this and open a new issue if needed. Have a nice day! :-)