laraveldockernginxwebsocketpusher-js

Laravel websockets with nginx


I have followed tutorials to configure laravel with websockets using docker, except that my php version is PHP 8.1.9, and laravel 8.

My nginx container is a reverse proxy to web_dev container, and does SSL termination. So further communication internally is over http.

nginx.conf

server {
    listen        80;
    server_name   mydomain.co.uk *.mydomain.co.uk;

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen        443 ssl;
    server_name   mydomain.co.uk *.mydomain.co.uk;

    include       common.conf;
    include       ssl.conf;

    location / {
        include     common_location.conf;
        proxy_pass  http://web_dev;
    }
}

server {
    listen        6001 ssl;
    server_name   mydomain.co.uk *.mydomain.co.uk;

    include       common.conf;
    include       ssl.conf;
        
    location /ws {
        proxy_read_timeout     60;
        proxy_connect_timeout  60;
        proxy_redirect         off;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_cache_bypass $http_upgrade;
        proxy_set_header    Sec-WebSocket-Key       'SGVsbG8sIHdvcmxkIQAAAA==';
        proxy_set_header    Sec-WebSocket-Version   '13';

        include            common_location.conf;
        proxy_pass         http://web_dev:6001/;
    }
}

Then I have a curl command:

curl -k \
    --no-buffer \
    --header "Connection: upgrade" \
    --header "Upgrade: websocket" \
    -v \
    https://mydomain.co.uk:6001/ws/app/mydomainkey

This is the output:

Connected to mydomain.co.uk (127.0.0.1) port 6001 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
  ....
* TLSv1.2 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET /ws/app/mydomainkey HTTP/1.1
> Host: mydomain.co.uk:6001
> User-Agent: curl/7.81.0
> Accept: */*
> Connection: upgrade
> Upgrade: websocket
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Mark bundle as not supporting multiuse
< HTTP/1.1 101 Switching Protocols
< Server: nginx/1.21.5
< Date: Sun, 04 Sep 2022 12:00:33 GMT
< Connection: upgrade
< Upgrade: websocket
< Sec-WebSocket-Accept: 5Tz1rM6Lpj3X4PQub3+HhJRM11s=
< X-Powered-By: Ratchet/0.4.4
< Strict-Transport-Security: max-age=31536000; includeSubDomains
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
�r{"event":"pusher:connection_established","data":"{\"socket_id\":\"155833011.137698690\",\"activity_timeout\":30}"}

This I think shows that the port 6001 and the SSL are configured correctly and that the websocket connection was established.

When I go to the url for websockets dashboard and click connect, I get

WebSocket connection to 'wss://mydomain.co.uk:6001/ws/app/bookhaircut?protocol=7&client=js&version=4.3.1&flash=false' failed:

enter image description here

I also tried fetch("wss://mydomain.co.uk:6001/ws/app/mydomainkey?protocol=7&client=js&version=4.3.1&flash=false"); which gives a different error:

enter image description here

On the web_dev container, I'm running supervisor which runs php artisan websockets:serve. I can verify that my web_dev container can connect to its websockets running service, because I ran in php artisan tinker:

`event (new \App\Events\NewTrade('test'))`
=> []

Then I checked supervisor logs I get many entries: enter image description here

So in conclusion:

  1. nginx is serving correctly because curl command works correctly with nginx with SSL (this also generates log entries in supervisor)
  2. laravel is internally connecting correctly
  3. But pusher-js in web browser is complaining that wss scheme is not supported and that websocket connection failed. My nginx version: nginx/1.21.5
  4. I'm not even using Echo yet. Echo uses pusher-js, and the dashboard is implemented using pusher-js. So if the dashboard doesn't work, nor will my Echo work.
  5. I have now down-graded my pusher to 4.3.1 in package.json but that didn't do anything because the version is hardcoded in the dashboard (also 4.3 though).

enter image description here

Any ideas?

  1. I tried to modify the dashboard to pull a different version https://cdnjs.cloudflare.com/ajax/libs/pusher/6.0.3/pusher.js And that didnt make any difference.

  2. Could nginx support wss instead of http? I have only seen configs with http. Configs with wss: throw an error.

So I'm out of ideas.

Here's my config files:

websockets.php

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

/* ssl is left empty.*/

related problems:


Solution

  • How I found out:

    I first tried to set SSL cert for the internal communication despite nginx doing termination (I had to try it) - but then the curl command broke, I couldn't connect to my websockets unless I removed my SSL certificates (was getting bad gateway).

    So I ended up with these:

    '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' => false,
            'host' => "127.0.0.1",
            'port' => 6001,
            'useTLS' => false,
            'scheme' => 'http',
        ],
    ],
    

    And in config/websockets.php

    'apps' => [
        [
            'id' => env('PUSHER_APP_ID'),
            'name' => env('APP_NAME'),
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'path' => "/ws",    //<-- important for nginx path detection
            'capacity' => null,
            'enable_client_messages' => false,
            'enable_statistics' => true,
        ],
    ],
    'ssl' => [
        'local_cert' => null,
        'local_pk' => null,
        'passphrase' => null,
    ],
    

    Then I noticed one thing about logging. When I got the websocket error in the browser, I was also receiving a new logging entry for my supervisor/websocket.log.

    I tried to remove the 'path' => "/ws", and I received the same exact browser error but no log entry.

    That gave me the fist hint when something is better than nothing. And I needed to stick to any settings that would not break this logging feature.

    Then I found that when I remove these lines from nginx.conf, it started all working, shockingly!!

        proxy_set_header    Sec-WebSocket-Key       'SGVsbG8sIHdvcmxkIQAAAA==';
        proxy_set_header    Sec-WebSocket-Version   '13';
    

    Originally I added these so I could simplify the curl command. But I didn't know that this would brake the browser's websockets. So the curl for testing now looks like this:

    curl -k \
        --no-buffer \
        --header "Connection: upgrade" \
        --header "Upgrade: websocket" \
        --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQAAAA==" \ //<-- this is random you can use it
        --header "Sec-WebSocket-Version: 13" \
        -v \
        https://mydomain.co.uk:6001/ws/app/mydomainkey