0

I'm trying to implement Laravel Websockets with multiple servers.

I have an App server and a Queue Worker server running. I tried to broadcast my notifications from the Queue Worker server but I'm getting

lluminate\Broadcasting\BroadcastException: Pusher error: . in /home/forge/my-app/vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php:128

In the App Server, in Network tab I can confirm it connects to the websocket. I've used alex bouma's post and setup reverse proxy. If I broadcast within the App Server, it works:

server {
    listen 6002 ssl http2;
    listen [::]:6002 ssl http2;
    server_name example.com;
    index.php

    location / {
        proxy_pass             http://127.0.0.1:6001;
        proxy_read_timeout     60;
        proxy_connect_timeout  60;
        proxy_redirect         off;
        
        # Allow the use of websockets
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

App Server & Queue Worker are in private networks and can connect to each other. However when I try to broadcast from the Queue Worker server, it doesn't work.

I'm having difficulty understanding how to make it work.

  • Do I have to run php artisan websockets:serve also on the queue worker? If so, do I need to give --host={private-ip} flag?

  • In my broadcasting.php, I have added App Server's private ip as PUSHER_ENDPOINT_HOST for Queue Worker's env. Is this correct?

'pusher' => [
      'driver' => 'pusher',
      'key' => env('PUSHER_APP_KEY'),
      'secret' => env('PUSHER_APP_SECRET'),
      'app_id' => env('PUSHER_APP_ID'),
      'options' => [
         'cluster' => env('PUSHER_APP_CLUSTER'),
         'encrypted' => in_array(config('app.env'), ['production', 'staging']),
         'host' => env('PUSHER_ENDPOINT_HOST', '127.0.0.1'),
         'port' => 6001,
         'scheme' => 'http'
      ],
],

Update: I have made changes as @KamleshPaul suggested. Here is how all my code looks like below.

I have these configs in both app server and worker server. I'm running php artisan websockets:serve on both, and App Server successfully connects to the socket. However, the queue worker doesn't send the notification.

Both php artisan websockets:serve shows: "Starting the WebSocket server on port 6001..."

But still it doesn't seem to work. (By the way, I'm using Laravel Forge and allowed ports are:

  • App: 6001, 6002, 433, 22

  • Worker: 6001, 6002, 22

.env

PUSHER_APP_ID=aaa
PUSHER_APP_KEY=bbb
PUSHER_APP_SECRET=ccc
PUSHER_APP_CLUSTER=mt1
PUSHER_HOST=socket.my_domain.com
PUSHER_PORT=433
PUSHER_SCHEME=https

VITE_PUSHER_APP_KEY=${PUSHER_APP_KEY}
VITE_PUSHER_APP_CLUSTER=${PUSHER_APP_CLUSTER}
VITE_PUSHER_HOST=${PUSHER_HOST}
VITE_PUSHER_SCHEME={PUSHER_SCHEME}
VITE_PUSHER_PORT={PUSHER_PORT}

echo.js

window.Echo = new Echo({
      broadcaster: 'pusher',
      key: import.meta.env.VITE_PUSHER_APP_KEY,
      cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
      wsHost: import.meta.env.VITE_PUSHER_HOST,
      wsPort: import.meta.env.VITE_PUSHER_PORT || 443,
      forceTLS: true,
      disableStats: true,
      scheme: import.meta.env.VITE_PUSHER_SCHEME,
      enabledTransports: ["ws", "wss"],
}

broadcasting.php

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'cluster' => env('PUSHER_APP_CLUSTER'),
        'encrypted' => true,
        'host' => env('PUSHER_HOST'),
        'port' => env('PUSHER_PORT'),
        'scheme' => env('PUSHER_SCHEME')
    ],
],

nginx of socket.my_domain.com

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/socket.my_domain.com/before/*;

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name socket.my_domain.com;
    server_tokens off;
    root /home/forge/socket.my_domain.com/public;

    # FORGE SSL (DO NOT REMOVE!)
    ssl_certificate /etc/nginx/ssl/socket.my_domain.com/1262458/server.crt;
    ssl_certificate_key /etc/nginx/ssl/socket.my_domain.com/1262458/server.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_dhparam /etc/nginx/dhparams.pem;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    # FORGE CONFIG (DO NOT REMOVE!)
    include forge-conf/socket.my_domain.com/server/*;

    location / {
        proxy_pass             http://127.0.0.1:6001;
        proxy_read_timeout     60;
        proxy_connect_timeout  60;
        proxy_redirect         off;
    
        # Allow the use of websockets
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/socket.my_domain.com-error.log error;

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/socket.my_domain.com/after/*;

senty
  • 12,385
  • 28
  • 130
  • 260
  • you need to set correct `host` ,`port` and `scheme` in `broadcasting.php` – Kamlesh Paul Dec 02 '21 at 05:12
  • Let's say I have AppServer 10.0.0.1:6001 (https) which works on its own. Also QueueServer 10.0.03. What would be the correct `host`, `port` and `scheme`? – senty Dec 02 '21 at 05:16
  • i don't think in port `6001` you can enable ssl ?, usually i do port `433` in subdomain like `socket.appname.com` – Kamlesh Paul Dec 02 '21 at 05:19
  • I have port 443 for my app server block. 6001 is reverse proxy for port 6002. Did you setup laravel websockets within multiple instances before? – senty Dec 02 '21 at 05:23
  • yes my `websockets` is independent nginx config – Kamlesh Paul Dec 02 '21 at 05:25

2 Answers2

0

as per laravel websockets doc

create a subdomain socket.yourapp.tld then create a nginx config like this

server {
  listen        443 ssl;
  listen        [::]:443 ssl;
  server_name   socket.yourapp.tld;

  # Start the SSL configurations
  ssl                  on;
  ssl_certificate      /etc/letsencrypt/live/socket.yourapp.tld/fullchain.pem;
  ssl_certificate_key  /etc/letsencrypt/live/socket.yourapp.tld/privkey.pem;

  location / {
    proxy_pass             http://127.0.0.1:6001;
    proxy_read_timeout     60;
    proxy_connect_timeout  60;
    proxy_redirect         off;

    # Allow the use of websockets
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}

then in broadcasting.php you need to change these

'host' => socket.yourapp.tld,
'port' => 433,
'scheme' => 'https'

same goes for javascript

export default new Echo({
  broadcaster: "pusher",
  key: "key",
  wsHost: process.env.REACT_APP_WS_HOST,
  wsPort: process.env.REACT_APP_WS_PORT || 443,
  forceTLS: process.env.REACT_APP_WS_PORT === 433,
  disableStats: true,
  enabledTransports: ["ws", "wss"],
  authorizer: (channel, options) => {
    return {
      authorize: (socketId, callback) => {
        axios
          .post(
            `${process.env.REACT_APP_MARKETPLACE_URL}broadcasting/auth`,
            {
              socket_id: socketId,
              channel_name: channel.name,
            },
            {
              headers: {
                Authorization: `Bearer ${token}`,
              },
            }
          )
          .then((response) => {
            callback(false, response.data);
          })
          .catch((error) => {
            callback(true, error);
          });
      },
    };
  },

ref link https://beyondco.de/docs/laravel-websockets/basic-usage/ssl#usage-with-a-reverse-proxy-like-nginx

Kamlesh Paul
  • 11,778
  • 2
  • 20
  • 33
  • Thanks for your reply. Can you please share your echo config too? I just setup the subdomain approach, but still can't make it work. Do you run `php artisan websockets:serve` on the queue worker server? If so, do you add any flags to it? – senty Dec 02 '21 at 06:41
  • @senty. added let me know if you have any issue and make sure you restart your service worker as well – Kamlesh Paul Dec 02 '21 at 06:43
  • Thanks - Do you only run `php artisan websockets:serve` on the app server or also in the queue worker? – senty Dec 02 '21 at 06:51
  • `queue worker` is req. in websocket to run event i have both setup – Kamlesh Paul Dec 02 '21 at 07:01
  • I updated the OP and put all my configs. Can you please take a look? – senty Dec 02 '21 at 07:18
  • @senty what error your getting ? and run `php artisan server` that server from where you want to expose websoket i guess both server connected to same database ? – Kamlesh Paul Dec 02 '21 at 07:20
  • There is no error. The queued jobs get success, however App doesn't receive any message from worker. Both servers are connected to the same database and they are in the same VPC. – senty Dec 02 '21 at 07:24
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/239764/discussion-between-kamlesh-paul-and-senty). – Kamlesh Paul Dec 02 '21 at 07:24
0

I finally figured it out. These settings below worked for me. Hopefully it saves you some time :)

I decided to use reverse proxy on subdomain approach like described in the docs.

For the subdomain's nginx, the only different thing from a default Laravel nginx config is the location / {}. Just copy the nginx config of your Laravel app, change the server_name and replace the location / {} and you are good to go.

window.Echo = new Echo({
     broadcaster: 'pusher',
     key: import.meta.env.VITE_PUSHER_APP_KEY,
     cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
     wsHost: import.meta.env.VITE_PUSHER_HOST,
     wsPort: import.meta.env.VITE_PUSHER_PORT || 443,
     forceTLS: true,
     disableStats: true,
     scheme: import.meta.env.VITE_PUSHER_SCHEME,
     enabledTransports: ["ws", "wss"],
});

// broadcasting.php

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'cluster' => env('PUSHER_APP_CLUSTER'),
        'encrypted' => true,
        'host' => env('PUSHER_HOST'),
        'port' => env('PUSHER_PORT'),
        'scheme' => env('PUSHER_SCHEME'),
    ],
],

// websockets.php

'apps' => [
    [
        'id' => env('PUSHER_APP_ID'),
        'name' => env('APP_NAME'),
        'key' => env('PUSHER_APP_KEY'),
        'secret' => env('PUSHER_APP_SECRET'),
        'host' => env('PUSHER_HOST'),
//        'capacity' => null,
        'enable_client_messages' => false,
        'enable_statistics' => false,
    ],
],

// .env file

PUSHER_APP_ID=HIDDEN_ID
PUSHER_APP_KEY=HIDDEN_KEY
PUSHER_APP_SECRET=HIDDEN_SECRET
PUSHER_APP_CLUSTER=mt1
PUSHER_HOST=socket.example.com;
PUSHER_PORT=6001
PUSHER_SCHEME=http

VITE_PUSHER_APP_KEY=${PUSHER_APP_KEY}
VITE_PUSHER_APP_CLUSTER=${PUSHER_APP_CLUSTER}
VITE_PUSHER_HOST=${PUSHER_HOST}
VITE_PUSHER_SCHEME=https
VITE_PUSHER_PORT=433
  • Make sure you run php artisan websockets:serve in both instances: in my case - App & Worker server.

  • Make sure you allow port 6001 on your app server

In my case, if I ssh into the server and write php artisan websockets:serve, it doesn't show the upcoming messages or connections like it do in local, so be aware of it.

  • In App Server, for connecting the Laravel Websockets dashboard just use empty port:

enter image description here


Some resources:

senty
  • 12,385
  • 28
  • 130
  • 260