Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set up an apache proxy for Meteor/SockJS and WebSocket?

I have an apache proxy for a meteor app and apache and meteor are on two separate machines. I need it that way as apache has to serve a lot of real websites and it wouldn't be a good idea to install the meteor app on this machine due to its limited resources.

However the WebSocket handshake fails with response code 400 "Can upgrade only to websocket" if I try to connect from the outside via the proxy. Everything works fine when I connect from within the LAN directly to the meteor machine. When WebSocket fails SockJS/Meteor falls back to XHR but unfortunately this brings up some bugs in the app in question. So I really need WebSocket to work in most of the cases.

I patched my apache installation with the patch mentioned here: https://stackoverflow.com/a/16998664 That looked like it went well but nothing changed...

My apache proxy directives currently are as follows:

ProxyRequests Off
ProxyPreserveHost On
ModPagespeed Off
<proxy>
Order deny,allow
Allow from all
</proxy>
ProxyPass / http://10.0.2.6:3000/
ProxyPassReverse / http://10.0.2.6:3000/

And I even know what's triggering the problem. The apache proxy messes around with the header. The original request header of the packet in question leaving my machine looks like this:

GET /sockjs/430/minw4r_o/websocket HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Pragma: no-cache
Cache-Control: no-cache
Sec-WebSocket-Key: myKey
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame
User-Agent: My Agent

While the packet gets forwarded from the apache proxy like this:

GET /sockjs/430/minw4r_o/websocket HTTP/1.1
Host: example.com
Origin: http://example.com
Pragma: no-cache
Cache-Control: no-cache
Sec-WebSocket-Key: myKey
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame
User-Agent: My Agent
X-Forwarded-For: 24.xxx.xxx.xxx
X-Forwarded-Host: example.com
X-Forwarded-Server: example.com
Connection: Keep-Alive

So "Upgrade" gets removed and "Connection" altered and so the websocket handshake fails. Now I could try to always set "Upgrade" to "websocket" with a RequestHeader directive. However this doesn't feel right and I guess it would bring up other problems and so I was wondering if there is a real solution to this problem? Or is this something the patch from https://stackoverflow.com/a/16998664 should address and something went wrong on my end applying it?

From what I have read switching to nginx could make this setup easier. I will consider this but when possible I'd like to do this with apache as nginx would make other things more complicated and cost me a lot of time.

like image 644
Jey DWork Avatar asked Feb 25 '14 01:02

Jey DWork


People also ask

Can you proxy WebSockets?

WebSocket communication can take successfully take place in the presence of forward proxies, providing the client and proxy server have been configured properly to deal with it. This page explains how to configure a Universal Messaging JavaScript client and Apache serving as a forward proxy to permit WebSocket use.

What is a WebSocket reverse proxy?

A reverse HTTP proxy over WebSocket is a type of proxies, which retrieves resources on behalf on a client from servers and uses the WebSocket protocol as a "tunnel" to pass TCP communication from server to client.

Is Apache a proxy server?

In addition to being a "basic" web server, and providing static and dynamic content to end-users, Apache httpd (as well as most other web servers) can also act as a reverse proxy server, also-known-as a "gateway" server.


2 Answers

We use this for Apache and a SockJS app behind Apache. Apache is doing WebSocket proxy automatically, but you have to rewrite the scheme to ws otherwise it fallbacks to XHR. But only if the connection is a WebSocket handshake. Adding the following will fix your problem :) (note: change the localhost:3000 accordingly to your own backend url.

RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^websocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade [NC]
RewriteRule .* ws://localhost:3000%{REQUEST_URI} [P]
like image 79
Fatih Arslan Avatar answered Nov 16 '22 04:11

Fatih Arslan


This answer is based on Fatih's answer. His solution fails for browsers that send a connection request header other than "Upgrade", such as "keep-alive, Upgrade". This was the case for me with Firefox 42.

To tackle the issue for Firefox also, change the apache RewriteCond as follows:

RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule .* ws://localhost:3000%{REQUEST_URI} [P]

(^Upgrade$ becomes Upgrade$)

I wanted to put this as a comment to Fatih's answer, however I lack the necessary reputation.

like image 20
derwiwie Avatar answered Nov 16 '22 02:11

derwiwie