socket.ioapache2reverse-proxymod-proxymod-proxy-wstunnel

socket.io https proxy config for apache2


I have a working socket.io setup with a JavaScript-client and a python-server. The client is basically just one line: Error during WebSocket handshake: Unexpected response code: 404

socket = io.connect('');

The server script can be found at https://github.com/miguelgrinberg/python-socketio/blob/master/examples/server/wsgi/app.py - I'm running the server on port 5000.

All you probably have to know about client and server is that they work great on my localhost apache installation and that socket.io starts with a GET requests and upgrades to a websocket from there. This is how a successfull handshake looks like:

http://localhost:5000/socket.io/?EIO=3&transport=polling&t=N1kLfSx
http://localhost:5000/socket.io/?EIO=3&transport=polling&t=N1kLfTI&sid=70ddae8b36da4f0f8f9a851fa2c0121e
ws://localhost:5000/socket.io/?EIO=3&transport=websocket&sid=70ddae8b36da4f0f8f9a851fa2c0121e

Now we have a websocket and the rest of the communication is running through that.

What I'm trying for two days now is to get the example running on my web server - so I'm looking for a working apache https config that proxies the requests to my backend server on port 5000.

I tried dozens of variations of configurations and none of them works - they all look very similar to this. The URL-rewriting part is meant to proxy the websocket connection once the initial handshake succeeded.

RewriteEngine On
RewriteCond %{REQUEST_URI}  ^/socket.io            [NC]
RewriteCond %{QUERY_STRING} transport=websocket    [NC]
RewriteRule /(.*)           ws://localhost:5000/$1 [P,L]

ProxyPass        /socket.io/ http://localhost:5000/socket.io/
ProxyPassReverse /socket.io/ http://localhost:5000/socket.io/

Apache does forward the first socket.io GET request to the server, the server replies:

    ÿ0{"sid":"2efa0dbbce4c4eef958e70b393639610","upgrades":["websocket"],"pingTimeout":60000,"pingInterval":25000}ÿ42["my_response",{"data":"Connected","count":0}]ÿ40

Then a websocket is opened and closed again right away (code 404). Then the client sends a POST request which fails (code 400). Then the client starts all over again with step 1: making a GET request, getting a new session id...

(The ÿXX special characters also appear in the working localhost communication - so they seem to belong there).

What am I doing wrong?

UPDATE - getting close to a solution!

I tried several Python and NodeJS implementations of an socket.io example server as well as several client scripts - all of them created the same problem which confirmed my thesis that Apache is the problem.

I installed Nginx and found a configuration that finally worked! These are the magic lines:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

I'm now using a debug-server that prints all received HTTP requests to compare the Nginx requests to the Apache2 requests with the goal to configure Apache2 to pass the same headers as Nginx.

The last challenge seems to be translating the Nginx config lines to Apache2. Sounds easier than it is.. that will be the key to the solution, though.


Solution

  • Finally! After a week of research I finally found a working solution:

    I put this code into the Section of my Apache2 config:

            ProxyRequests off
            ProxyVia on
            RewriteEngine On
    
            RewriteEngine On
            RewriteCond %{HTTP:Connection} Upgrade [NC]
            RewriteCond %{HTTP:Upgrade} websocket [NC]
            RewriteRule /(.*) ws://127.0.0.1:8080/$1 [P,L]
    
            ProxyPass               / http://127.0.0.1:5000/
            ProxyPassReverse        / http://127.0.0.1:5000/
    

    There's a million configurations out there and I tried them all - this is the only one that worked for me. The problem was, that Apache2 didn't forward the "Upgrade=websocket" and "Connection=upgrade" headers. The given rewrite commands make Apache2 add these headers to the proxied connection.

    While researching this I first found a working Nginx configuration which I then translated to Apache2. So if you have that problem with Nginx, you can use this here:

            location / {
                    proxy_pass http://127.0.0.1:5000/;
                    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;
            }
    

    This little NodeJS TCP Server helped me a lot with debugging the HTTP headers: https://riptutorial.com/node-js/example/22405/a-simple-tcp-server