3

Software:

In websockets.php (complete file) I have my local_cert and local_pk setup with my certificates. If I leave this option blank I cannot even connect. I also have set verify_peerto false, because if I don't I cannot connect either.

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'),
            'host' => '127.0.0.1',
            'port' => 6001,
            'scheme' => 'https',
            'curl_options' => [
                CURLOPT_SSL_VERIFYHOST => 0,
                CURLOPT_SSL_VERIFYPEER => 0,
            ]
        ],
    ],

If I get rid of the curl options I get an empty Broadcast exception like described here.

bootstrap.js:

window.Pusher = require('pusher-js');
window.Echo = new Echo({
    broadcaster: 'pusher',
    key: '7d23096ae0ab2d02d220',
    wsHost: window.location.hostname,
    wsPort: 6001,
    wssPort: 6001,
    encrypted: true,
    disableStats: true,
    auth: {
        headers: {
            'X-CSRF-TOKEN': window.App.csrfToken,
        },
    },
})

This is all I get from the logs after running php artisan websockets:serve:

New connection opened for app key 7d23096ae0ab2d02d220.
Connection id 49092664.114416323 sending message {"event":"pusher:connection_established","data":"{\"socket_id\":\"49092664.114416323\",\"activity_timeout\":30}"}

What I should get is messages about listening / joining channels and sending messages etc. But all of that does not work at the moment. I have things like:

Echo.private('notifications.' + this.user.id)
                .listen('UserNotificationSent', (e) => {
                    console.log(e)
                })

Events: UserNotificationSent.php for example.

Of course internally I have everything else setup as well: channels with auth, etc. Everything worked locally on my machine on a lower Laravel version (5.4). But I recently updated to 5.8 and deployed to a server and now I struggle with this.

I also opened an issue on github.

IMPORTANT UPDATE This is actually not due to the deployment, I have the same problem on my local setup. What is interesting is that listening to channels via Echo.channel() works, however, .private() is not working. On Github (link above) I came across a guy who has the exact same problem. We did not find a solution yet.

Hillcow
  • 890
  • 3
  • 19
  • 48
  • 1
    what's the code in your Event? – Grey Nov 12 '19 at 10:35
  • @Grey: for example: https://textuploader.com/1od1t – Hillcow Nov 12 '19 at 11:18
  • please add this to your question so people can easily find it – Grey Nov 12 '19 at 11:21
  • Are you using a reverse proxy? If your websocket server connects to your app locally, you most likely don't need https at all. And how does your `config/websockets.php` look like? – Namoshek Nov 14 '19 at 20:38
  • @Namoshek Thank you, I wasn't sure whether or not I need https. Anyway, I tried this with http instead too and got the same result. Here is my `websockets.php`: https://textuploader.com/1otrn – Hillcow Nov 14 '19 at 21:16
  • @Namoshek Honestly I don't know how to use this without ssl. No matter what I do, Laravel Echo always seems to use `wss://` and not `ws://`. My website uses `https://` with LetsEncrypt. – Hillcow Nov 15 '19 at 09:37

3 Answers3

1

It happens because of the port 6001 is reserved in nginx on live server (explanation at the bottom). I needed to use reverse-proxy on nginx to make it work - and used port 6002 for websockets in live server.

In nginx (upon request, I added the full nginx code):

server {

  #The nginx domain configurations
  root /var/www/laravel/public;
  index index.html index.htm index.php index.nginx-debian.html;
  server_name example.com www.example.com;

  #WHAT YOU NEED IS FROM HERE...
  location / {
      try_files $uri $uri/ /index.php?$query_string;

      # "But why port 6000, 6002 and 433? Scroll at the bottom"

      proxy_pass                          http://127.0.0.1:6001;
      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  https;
      proxy_set_header X-VerifiedViaNginx yes;
      proxy_read_timeout                  60;
      proxy_connect_timeout               60;
      proxy_redirect                      off;

      # Specific for websockets: force the use of HTTP/1.1 and set the Upgrade header
      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;
 }
 #..UNTIL HERE - The rest are classic nginx config and certbot

 #The default Laravel nginx config
 location ~ \.php$ {
      try_files $uri =404;
      fastcgi_split_path_info ^(.+\.php)(/.+)$;
      fastcgi_pass unix:/run/php/php7.2-fpm.sock;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      include fastcgi_params;
 }

 #SSL by certbot
 listen [::]:443 ssl ipv6only=on; # managed by Certbot
 listen 443 ssl; # managed by Certbot

 ssl                         on;
 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
 include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

 ssl_session_cache           shared:SSL:30m;
 ssl_protocols               TLSv1.1 TLSv1.2;

 # Diffie Hellman performance improvements
 ssl_ecdh_curve              secp384r1;
}

Everything that connects to your domain over TLS will be proxied to a local service on port 6001, in plain text. This offloads all the TLS (and certificate management) to Nginx, keeping your websocket server configuration as clean and simple as possible.

This also makes automation via Let’s Encrypt a lot easier, as there are already implementations that will manage the certificate configuration in your Nginx and reload them when needed. - Source - Mattias Geniar

Echo setup:

let isProduction = process.env.MIX_WS_CONNECT_PRODUCTION === 'true';

Vue.prototype.Echo = new LaravelEcho({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    wssHost: window.location.hostname,
    wssPort: isProduction ? 6002 : 6001,
    wsHost: window.location.hostname,
    wsPort: isProduction ? 6002 : 6001,
    disableStats: false,
    encrypted: isProduction,
    enabledTransports: ['ws', 'wss'],
    disabledTransports: ['sockjs', 'xhr_polling', 'xhr_streaming']
});

In websockets.php

'apps' => [
    [
        'id' => env('MIX_PUSHER_APP_ID'),
        'name' => env('APP_NAME'),
        'key' => env('MIX_PUSHER_APP_KEY'),
        'secret' => env('MIX_PUSHER_APP_SECRET'),
        'enable_client_messages' => false,
        'enable_statistics' => true,
    ],
],

// I kept them null but I use LetsEncrypt for SSL certs too.
'ssl' => [
   'local_cert' => null,
   'local_pk' => null,
   'passphrase' => null,
]

And broadcasting.php

'pusher' => [
     'driver' => 'pusher',
     'key' => env('MIX_PUSHER_APP_KEY'),
     'secret' => env('MIX_PUSHER_APP_SECRET'),
     'app_id' => env('MIX_PUSHER_APP_ID'),
     'options' => [
         'cluster' => env('MIX_PUSHER_APP_CLUSTER'),
         'encrypted' => env('MIX_WS_CONNECT_PRODUCTION'),
         'host' => '127.0.0.1',
         'port' => env('MIX_WS_CONNECT_PRODUCTION') ? 6002 : 6001,
         'scheme' => 'http'
     ],
 ],

This was my full cycle that made it work. Hope it helps.


Quoting from Alex Bouma's explanation:

"But why port 6000, 6002 and 433, what a mess!?"

I hear ya! Let me explain a bit, it will hopefully all make sense afterwards.

Here is the thing, opening an port on your server can only be done by only one application at a time (technically that is not true, but let's keep it simple here). So if we would let NGINX listen on port 6001 we cannot start our websockets server also on port 6001 since it will conflict with NGINX and the other way around, therefore we let NGINX listen on port 6002 and let it proxy (NGINX is a reverse proxy after all) all that traffic to port 6001 (the websockets server) over plain http. Stripping away the SSL so the websockets server has no need to know how to handle SSL.

So NGINX will handle all the SSL magic and forward the traffic in plain http to port 6001 on your server where the websockets server is listening for requests.

The reason we are not configuring any SSL in the websockets.php config and we define the scheme in our broadcasting.php as http and use port 6001 is to bypass NGINX and directly communicate with the websockets server locally without needing SSL which faster (and easier to configure and maintain).

Bruno Rohée
  • 3,436
  • 27
  • 32
senty
  • 12,385
  • 28
  • 130
  • 260
  • Thanks for this. Is your location block on nginx part of first or second server block? Would you mind posting your full nginx please? Also, are you using the root domain, or a second dedicted subdomain for sockets? – Dazzle Nov 16 '19 at 12:18
  • @senty Thank you, but when I apply your nginx settings my console says: `GET https://www.test.com/js/app.js net::ERR_ABORTED 404`. So I cannot access any files of my website any longer. – Hillcow Nov 16 '19 at 12:44
  • By the way I just pasted your nginx config, because I have no clue about nginx. – Hillcow Nov 16 '19 at 12:45
  • @Dazzle It's only the location part of the nginx: `location / {}` @Hillcow I couldn't find your nginx, can you link it as a comment? – senty Nov 16 '19 at 13:01
  • I added the full nginx (except the redirect part of certbot which is automatically generated by certbot - which is optional so I didnt put it in). – senty Nov 16 '19 at 13:08
  • Just got this working finally after signing up for a new pusher account and clearing config cache – Dazzle Nov 16 '19 at 13:13
  • @senty as I mentioned in my post I am using Plesk to manage my server and the nginx config gets auto-generated - it is quite big distributed over a couple of files. – Hillcow Nov 16 '19 at 13:31
  • I just added your part to overwrite this config. – Hillcow Nov 16 '19 at 13:32
  • So you start the websocket server on port 6002 on prod, right? I'm not sure why in your nginx it says 6001 and in your other config it is 6002. Or is the nginx just for local development? – Hillcow Nov 16 '19 at 14:41
  • This is production nginx. `proxy_pass http://127.0.0.1:6001;` - You can read about it [here](https://github.com/beyondcode/laravel-websockets/issues/153#issuecomment-545018950) - _"So NGINX will handle all the SSL magic and forward the traffic in plain http to port 6001 on your server where the websockets server is listening for requests."_ - I've been there and I was banging my head to the walls until I figured out why it worked out – senty Nov 16 '19 at 15:08
  • I didn't remove the `isProduction` flags so you can see how I managed to work the code in my local & in production. In local 6001 works just fine for me too. – senty Nov 16 '19 at 15:22
  • @senty and where do you tell nginx to listen to port 6002? – Hillcow Nov 19 '19 at 09:05
  • @senty However, this is all good but it does not resolve my problem, because it doesn't work for me locally either. I have the same problem on my local setup. – Hillcow Nov 19 '19 at 09:23
  • @senty Please have a look at my post update at the bottom of my initial question. – Hillcow Nov 19 '19 at 09:35
  • I found the error, thank you so much for your help anyway! – Hillcow Nov 19 '19 at 11:49
  • Great! Would be nice to hear your solution too though :) – senty Nov 19 '19 at 17:29
  • @senty You find it as the answer to this question :D – Hillcow Nov 20 '19 at 07:29
1

I found the problem.

I had this in my web.php:

Route::post('/broadcasting/auth', function (Illuminate\Http\Request $req) {
if ($req->channel_name == 'users') {
    return Broadcast::auth($req);
}
});

I don't exactly remember why and when I added this, but it was probably from here. For some reason this didn't generate any errors. Anyway, I got rid of this and now it´s working like a charm.

Hillcow
  • 890
  • 3
  • 19
  • 48
-3

Use broadcastAs() method with event

public function broadcastAs()
{
    return 'UserNotificationSent';
}

And listen it like this.

.listen('.UserNotificationSent', function (e) {
    ....
});

Put dot(.) before UserNotificationSent event to listen it

see here https://laravel.com/docs/6.x/broadcasting#broadcast-name

  • I don't see how this solves my problem. I use the event's class name just as stated in the documentation. Renaming the event shouldn't do anything. – Hillcow Nov 15 '19 at 16:38
  • @Hillcow It is actually not renaming the event class name. It is used for the purpose when you listing to the event in the global namespace. Put the **dot(.)** before the event name. – Furqan Ansari Nov 15 '19 at 16:45
  • Yes, but I'm not using the `broadcastAs()` method - which means I don't need the dot. Also, you linked to 6.x documentation and I'm still on 5.8, just as a note. – Hillcow Nov 15 '19 at 17:07
  • https://laravel.com/docs/5.8/broadcasting#broadcast-name, please give it a try, I have also implemented web sockets in my app laravel 5.8 app and listing event with dot. – Furqan Ansari Nov 18 '19 at 09:47