Search code examples
node.jsapachewebsocketmod-proxymod-proxy-wstunnel

How can I configure Apache to correctly forward websockets?


I have already looked at this question and various others on both SO and related sites, but none of the solutions suggested so far have worked.

I am running tty.js on localhost:8080 and verified that websockets work correctly when hitting that port directly. Now, I am attempting to connect to tty.js through an Apache reverse proxy. The application can work without using Websockets, but I am trying to understand why Websockets do not work.

Here is my Apache configuration for testing on localhost with a fresh Apache build from source.

Listen 9000
# Load proxy modules
LoadModule proxy_module "modules/mod_proxy.so"
LoadModule proxy_http_module "modules/mod_proxy_http.so"
LoadModule proxy_wstunnel_module "modules/mod_proxy_wstunnel.so"

<VirtualHost *:9000>
ServerName localhost

ProxyPass /tty/socket.io/1/ ws://localhost:8080/socket.io/1/
ProxyPassReverse /tty/socket.io/1/ ws://localhost:8080/socket.io/1/
ProxyPass /tty/ http://localhost:8080/
ProxyPassReverse /tty/ http://localhost:8080/

</VirtualHost>

When I connect to http://localhost:9000/tty/, I get a 500 error.

In the server side logs, I get the following error.

[Mon Jan 02 19:30:32.342551 2017] [proxy:warn] [pid 28226:tid 140098955872000] [client 127.0.0.1:38372] AH01144: No protocol handler was valid for the URL /tty/socket.io/1/. If you are using a DSO version of mod_proxy, make sure the proxy submodules are included in the configuration using LoadModule., referer: http://localhost:9000/tty/

What is the correct configuration for Apache to make Websockets work correctly?


Solution

  • After several hours of running Apache in a debugger, I discovered that Apache was faithfully passing all the requests that matched the given path through mod_proxy, including the following one.

    http://localhost:9000/tty/socket.io/1/
    

    It turns out that this request goes out before the actual request with the Upgrade: Websocket header, and it is a normal AJAX request.

    When this request gets passed to mod_proxy_wstunnel, the module notices that the Websocket header is missing and declines to handle it, causing Apache to claim that No protocol handler was valid in the log message and return status code 500.

    The fix is to extend the path of the ProxyPath directive so that it only covers actual Websocket requests, and not stray Ajax requests.

    ProxyPass /tty/socket.io/1/websocket/ ws://localhost:8080/socket.io/1/websocket/
    ProxyPassReverse /tty/socket.io/1/websocket/ ws://localhost:8080/socket.io/1/websocket/