You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@httpd.apache.org by Damien Clark <da...@gmail.com> on 2017/09/07 13:48:33 UTC

[users@httpd] RE: Web sockets & proxypass - No protocol handler was valid for the URL

Hi Eric/Adam,

Just reviving an old thread.  Here is the full thread archive if it helps. http://apache-http-server.18135.x6.nabble.com/Web-sockets-amp-proxypass-No-protocol-handler-was-valid-for-the-URL-td5033887.html

I am using Apache virtual host as frontend, and Node.js as backend running express and socket.io

I’ve come into the same issue as you Adam.  

Node.js Socket.io library uses the path /socket.io/ for both web socket and non-websocket requests where the difference is in the query parameter, rather than the path.  This means you can’t differentiate by simply using ProxyPass or ProxyPassMatch - both only match the path (not query).  

I’m using an adaptation of Eric’s rewrite rule from the bottom of this email (only difference is just http and ws, rather than https and wss).

RewriteEngine on
RewriteCond %{HTTP:Upgrade} "(?i)websocket"
RewriteRule ^/(.*)$ ws://localhost:3000/$1 [P]
ProxyPass        / http://localhost:3000/

I have confirmed that the request makes it to the localhost:3000, but with the upgrade header missing.

I have also confirmed that the client request from the browser does have the upgrade header.  

I am wondering whether the use of a rewriterule with the “P” flag is the reason the upgrade header hasn’t been included.  I wonder this because it is pretty widely reported to work fine when using ProxyPass to ws uri.

So Eric, do you think this is this a bug, or a misconfiguration on my part?

Following is the diagnostics I have performed.

Here are screenshots from Mozilla Firefox, and also Chromium showing that the upgrade headers were sent from these browsers.

https://www.evernote.com/l/AF1VDE7CjrNFeY5BsM1MlNCktM_RaqUBbgE
https://www.evernote.com/l/AF3TRPlHiNlBKK0SShHOqwPsKCuJNxUzUdE

The “Connection” header has “Upgrade” and the “Upgrade” header has “websocket” for both browsers.

Here is an excerpt from a packet capture between apache and node.js via localhost port 3000 as per this rewrite (I know as the transport=websocket query parameter matches the request from the browser).

—

GET /socket.io/?EIO=3&transport=websocket&sid=-hLhxB1f2Cdu0brhAAA_ HTTP/1.1
Host: buzzer.click
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-AU,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: http://buzzer.click
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: kRvxOrD3J3GpnliwvReJ8Q==
Cookie: io=-hLhxB1f2Cdu0brhAAA_
Pragma: no-cache
Via: 1.1 router (squid/3.4.8), 1.1 buzzer.click (Apache/2.4.27)
X-Forwarded-For: 192.168.100.9, 119.18.39.57
Cache-Control: no-cache
X-Forwarded-Host: buzzer.click
X-Forwarded-Server: buzzer.click
Connection: Keep-Alive

—

and response from node.js

—

HTTP/1.1 400 Bad Request
Content-Type: application/json
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://buzzer.click
Date: Thu, 07 Sep 2017 12:05:07 GMT
Connection: keep-alive
Transfer-Encoding: chunked

22
{"code":3,"message":"Bad request"}
0

—

Output from node.js console with debugging enabled:

—

  engine setting new request for existing client +0ms
  engine:polling setting request +1ms
  engine intercepting request for path "/socket.io/" +64ms
  engine handling "GET" http request "/socket.io/?EIO=3&transport=websocket&sid=Fv3505Tz0oOu7J3AAAAr" +0ms
  engine bad request: unexpected transport without upgrade +0ms

—

Eric, do you have any suggestions for things I may have done wrong, or other tests I could perform?

Many thanks.

Regards,
Damien.

On Wed, Dec 28, 2016 at 3:53 PM, Eric Covener <[hidden email]> wrote:

> On Tue, Dec 27, 2016 at 8:39 AM, Adam Teale <[hidden email]> wrote: 
>> Hi! 
>> 
>> I've been trying to setup a reverse proxy to a localhost websocket url. 
>> 
>> ProxyPass /chat/stream/ wss://localhost:8000/chat/stream/ 
>> ProxyPassReverse /chat/stream/ wss://localhost:8000/chat/stream/ 
>> 
>> I get an error in the apache error_log that reads: 
>> 
>> No protocol handler was valid for the URL /chat/stream/. If you are using a 
>> DSO version of mod_proxy, make sure the proxy submodules are included in the 
>> configuration using LoadModule. 
>> 
>> I have read a lot of pages via google of people using this method so I 
>> wonder if there is some issue in our setup/install of Apache that ships with 
>> Mac OS X 10.11 & Server.app 5.2? 
>> 
>> I have all the standard modules loaded in httpd_server_app.conf 
>> 
>> LoadModule proxy_module libexec/apache2/mod_proxy.so 
>> LoadModule proxy_http_module libexec/apache2/mod_proxy_http.so 
>> LoadModule proxy_wstunnel_module libexec/apache2/mod_proxy_wstunnel.so 
>> 
>> When I access the application running on localhost:8000 directly on the 
>> server everything works fine 
>> 
>> Any ideas what could be going on? 
> 
> There is a bug in this area, but you need to decide what you expect to 
> happen with non-websockets requests to /chat/stream/ which is what's 
> happening here. 
> 
> If you intend to proxy it, you might need to change the LoadModule 
> order of mod_proxy_http and mod_proxy_wstunnel to try to get a 
> different order at runtime. 
> 
> If you expect to satisfy it somehow else... you have a bit of a 
> puzzler.  I'm not sure there's a good recipe for this.  Otherwise as 
> Yann said, you should use different URLs if you can.
«  [hide part of quote]

For the record (after private discussion with Adam), it seems that a 
configuration like the below would work for http(s) and ws(s) on the 
same URL: 

  RewriteEngine on 
  RewriteCond %{HTTP:Upgrade} "(?i)websocket" 
  RewriteRule ^/(.*)$ wss://backend/$1 [P] 
  ProxyPass / https://backend/

Actually it didn't work for him because of other app issues (Upgrade 
missing), but httpd behaved correctly with this. 

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
For additional commands, e-mail: users-help@httpd.apache.org


Re: [users@httpd] RE: Web sockets & proxypass - No protocol handler was valid for the URL

Posted by Damien Clark <da...@gmail.com>.
> On 7 Sep 2017, at 11:53 pm, Eric Covener <co...@gmail.com> wrote:
> 
>> 
>> I am wondering whether the use of a rewriterule with the “P” flag is the reason the upgrade header hasn’t been included.  I wonder this because it is pretty widely reported to work fine when using ProxyPass to ws uri.
>> 
> 
> I will have to look more later, but what's your exact httpd version? I

Yes, sorry.  Should have included this info.  

httpd -V
Server version: Apache/2.4.27 (CentOS)
Server built:   Jul 10 2017 09:38:04
Server's Module Magic Number: 20120211:68
Server loaded:  APR 1.5.2, APR-UTIL 1.5.4
Compiled using: APR 1.5.2, APR-UTIL 1.5.4
Architecture:   64-bit
Server MPM:     event
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)

> think if Upgrade is not preserved it implies mod_proxy_http was used
> via either ProxyPass or some failure in mod_proxy_wstunnel taking over
> -- so LogLevel trace8 might help.

I’ll generate the log output and get back to you.

Thanks Eric.

Cheers,
Damo.

> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
> For additional commands, e-mail: users-help@httpd.apache.org
> 


Re: [users@httpd] RE: Web sockets & proxypass - No protocol handler was valid for the URL

Posted by Damien Clark <da...@gmail.com>.
G’day Eric,

Thanks so much for taking the time to generate a working config.

Initially it didn’t work.

So I performed a packet capture outbound from my browser (which showed the Upgrade header), and also inbound to the apache process and determined that the ‘Upgrade’ header was lost somewhere in between.  Turned out to be my Squid proxy server as per this topic.  

http://lists.squid-cache.org/pipermail/squid-users/2017-January/013953.html <http://lists.squid-cache.org/pipermail/squid-users/2017-January/013953.html>

After bypassing the squid cache, your config below (and the SetEnvIf previously) worked perfectly.

My sincere thanks Eric.

Cheers,
D.


> On 11 Sep 2017, at 8:51 am, Eric Covener <co...@gmail.com> wrote:
> 
> For me, the setenvif worked but the overall test failed. I had misread
> something in how mod_proxy and mod_rewrite depend on eachother.
> 
> I don't know why your SetEnvIf didn't fire.
> 
> Here's what seemed to work for me:
> 
>  RewriteEngine ON
> 
>  RewriteCond %{HTTP:Upgrade} "(?i)websocket"
>  RewriteRule ^/(.*)$ ws://localhost:3000/$1 [P,L]
> 
>  RewriteRule ^/(.*)$ http://localhost:3000/$1 [P]
>  # Force a worker to be created w/o ProxyPass
>  <Proxy "http://localhost:3000">
>    ProxySet connectiontimeout=5
>  </Proxy>
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
> For additional commands, e-mail: users-help@httpd.apache.org
> 


Re: [users@httpd] RE: Web sockets & proxypass - No protocol handler was valid for the URL

Posted by Damien Clark <da...@gmail.com>.
Hi Eric,

Thanks so much for taking the time to generate a working config.

Initially it didn’t work (the RewriteCond was not matching), so I performed a packet capture on the request leaving my browser, and compared it with a packet capture arriving at the apache process.  This revealed that the Upgrade header was set by browser (as previously noted), but was missing when request arrived at Apache process.

Turns out that my Squid proxy server was stripping off the Upgrade header, as per this discussion thread here:

http://lists.squid-cache.org/pipermail/squid-users/2017-January/013953.html <http://lists.squid-cache.org/pipermail/squid-users/2017-January/013953.html>

After bypassing squid, the config worked perfectly.  

Thanks again for your assistance Eric.

Regards,
Damien.

> On 11 Sep 2017, at 8:51 am, Eric Covener <co...@gmail.com> wrote:
> 
> For me, the setenvif worked but the overall test failed. I had misread
> something in how mod_proxy and mod_rewrite depend on eachother.
> 
> I don't know why your SetEnvIf didn't fire.
> 
> Here's what seemed to work for me:
> 
>  RewriteEngine ON
> 
>  RewriteCond %{HTTP:Upgrade} "(?i)websocket"
>  RewriteRule ^/(.*)$ ws://localhost:3000/$1 [P,L]
> 
>  RewriteRule ^/(.*)$ http://localhost:3000/$1 [P]
>  # Force a worker to be created w/o ProxyPass
>  <Proxy "http://localhost:3000">
>    ProxySet connectiontimeout=5
>  </Proxy>
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
> For additional commands, e-mail: users-help@httpd.apache.org
> 


Re: [users@httpd] RE: Web sockets & proxypass - No protocol handler was valid for the URL

Posted by Eric Covener <co...@gmail.com>.
For me, the setenvif worked but the overall test failed. I had misread
something in how mod_proxy and mod_rewrite depend on eachother.

I don't know why your SetEnvIf didn't fire.

Here's what seemed to work for me:

  RewriteEngine ON

  RewriteCond %{HTTP:Upgrade} "(?i)websocket"
  RewriteRule ^/(.*)$ ws://localhost:3000/$1 [P,L]

  RewriteRule ^/(.*)$ http://localhost:3000/$1 [P]
  # Force a worker to be created w/o ProxyPass
  <Proxy "http://localhost:3000">
    ProxySet connectiontimeout=5
  </Proxy>

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
For additional commands, e-mail: users-help@httpd.apache.org


Re: [users@httpd] RE: Web sockets & proxypass - No protocol handler was valid for the URL

Posted by Damien Clark <da...@gmail.com>.
> On 8 Sep 2017, at 2:04 am, Eric Covener <co...@gmail.com> wrote:
> 
> On Thu, Sep 7, 2017 at 10:19 AM, Damien Clark <da...@gmail.com> wrote:
>> 
>>> On 7 Sep 2017, at 11:53 pm, Eric Covener <co...@gmail.com> wrote:
>>> 
>>>> 
>>>> I am wondering whether the use of a rewriterule with the “P” flag is the reason the upgrade header hasn’t been included.  I wonder this because it is pretty widely reported to work fine when using ProxyPass to ws uri.
> 
> Since you have 2.4.27, can you try (untested -- please log
> %{no-proxy}e to make sure nothing subtle is wrong)

The logs don’t seem to be showing that the env variable is properly being set.  I checked to make sure ‘setenvif’ module is loaded.

Here is the LogFormat I set:

LogFormat "%t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %{no-proxy}e" debug
CustomLog "logs/debug.log" debug

Excerpt from the log comparing two different request types - one with the upgrade (transport=websocket), the other without:

[08/Sep/2017:08:56:36 +1000] "GET /socket.io/?EIO=3&transport=polling&t=LvUZo-4 HTTP/1.1" 200 101 "http://buzzer.click/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0" -
[08/Sep/2017:08:56:36 +1000] "GET /socket.io/?EIO=3&transport=websocket&sid=2CxfW3Fj5pDd7mpWAAAf HTTP/1.1" 400 34 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:55.0) Gecko/20100101 Firefox/55.0” -

The no-proxy is unset for both (denoted by the ‘-' I assume).  I’ve double checked and the transport=websocket request from the browser definitely has the Upgrade: websocket header, while the former definitely does not.

Here is the apache config I am using so you can see exactly what I have done:

<VirtualHost buzzer.click:80>
   ServerAdmin webmaster@localhost
   ServerName buzzer.click
 
   DocumentRoot /var/www/buzzer.click/www

   <Directory /var/www/buzzer.click/www>
      Options -Indexes +FollowSymLinks
      AllowOverride None
      Require all granted
   </Directory>
 
   <Proxy *>
      Require all granted
   </Proxy>

    ProxyRequests Off
    ProxyPreserveHost On
    ProxyVia Full

  # Don't allow ProxyPass to find these
  SetEnvIf Upgrade (?i)websocket no-proxy=1

  RewriteEngine on
  RewriteCond %{HTTP:Upgrade} "(?i)websocket"
  RewriteRule ^/(.*)$ ws://localhost:3000/$1 [P]

  ProxyPass        / http://localhost:3000/
 
   LogFormat "%t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %{no-proxy}e" debug
   CustomLog "logs/debug.log" debug
 
   LogLevel warn
</VirtualHost>

Have I missed something?

Thanks again for your help Eric.

D.

> 
> # Don't allow ProxyPass to find these
> SetEnvIf Upgrade (?i)websocket no-proxy=1
> 
> RewriteRule ... [P,E=no-proxy:1] is too late unfortunately.
> 
> no-proxy should not block RewriteBased [P] flag, only the ProxyPass.
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
> For additional commands, e-mail: users-help@httpd.apache.org
> 


Re: [users@httpd] RE: Web sockets & proxypass - No protocol handler was valid for the URL

Posted by Eric Covener <co...@gmail.com>.
On Thu, Sep 7, 2017 at 10:19 AM, Damien Clark <da...@gmail.com> wrote:
>
>> On 7 Sep 2017, at 11:53 pm, Eric Covener <co...@gmail.com> wrote:
>>
>>>
>>> I am wondering whether the use of a rewriterule with the “P” flag is the reason the upgrade header hasn’t been included.  I wonder this because it is pretty widely reported to work fine when using ProxyPass to ws uri.

Since you have 2.4.27, can you try (untested -- please log
%{no-proxy}e to make sure nothing subtle is wrong)

# Don't allow ProxyPass to find these
SetEnvIf Upgrade (?i)websocket no-proxy=1

RewriteRule ... [P,E=no-proxy:1] is too late unfortunately.

no-proxy should not block RewriteBased [P] flag, only the ProxyPass.

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
For additional commands, e-mail: users-help@httpd.apache.org


Re: [users@httpd] RE: Web sockets & proxypass - No protocol handler was valid for the URL

Posted by Damien Clark <da...@gmail.com>.
> On 7 Sep 2017, at 11:53 pm, Eric Covener <co...@gmail.com> wrote:
> 
>> 
>> I am wondering whether the use of a rewriterule with the “P” flag is the reason the upgrade header hasn’t been included.  I wonder this because it is pretty widely reported to work fine when using ProxyPass to ws uri.
>> 
> 
> I will have to look more later, but what's your exact httpd version? I
> think if Upgrade is not preserved it implies mod_proxy_http was used
> via either ProxyPass or some failure in mod_proxy_wstunnel taking over
> -- so LogLevel trace8 might help.

I have attached the complete log output as a plain text file, but I think this might be the bit you are looking for (see below).  It appears mod_proxy_http handled the request via the rewriterule with the ‘p’ flag.  To preserve the necessary headers, how might I force the request to be handled by mod_proxy_wstunnel?  Or am I looking at this all wrong?

Thanks again Eric.

D.

[Fri Sep 08 00:09:26.808052 2017] [mpm_event:trace6] [pid 2078:tid 139884475877120] event.c(1434): connections: 2 (clogged: 0 write-completion: 0 keep-alive: 0 lingering: 0 suspended: 0)
[Fri Sep 08 00:09:27.078989 2017] [proxy_http:error] [pid 2078:tid 139884492662528] (70007)The timeout specified has expired: [client 119.18.39.57:53016] AH01102: error reading status line from remote server localhost:3000, referer: http://buzzer.click/
[Fri Sep 08 00:09:27.079072 2017] [proxy:error] [pid 2078:tid 139884492662528] [client 119.18.39.57:53016] AH00898: Error reading from remote server returned by /socket.io/, referer: http://buzzer.click/
[Fri Sep 08 00:09:27.601732 2017] [proxy_http:error] [pid 2078:tid 139884626945792] (70007)The timeout specified has expired: [client 119.18.39.57:53019] AH01102: error reading status line from remote server localhost:3000, referer: http://buzzer.click/
[Fri Sep 08 00:09:27.601795 2017] [proxy:error] [pid 2078:tid 139884626945792] [client 119.18.39.57:53019] AH00898: Error reading from remote server returned by /socket.io/, referer: http://buzzer.click/


Re: [users@httpd] RE: Web sockets & proxypass - No protocol handler was valid for the URL

Posted by Eric Covener <co...@gmail.com>.
> I’m using an adaptation of Eric’s rewrite rule from the bottom of this email (only difference is just http and ws, rather than https and wss).
>
> RewriteEngine on
> RewriteCond %{HTTP:Upgrade} "(?i)websocket"
> RewriteRule ^/(.*)$ ws://localhost:3000/$1 [P]
> ProxyPass        / http://localhost:3000/
>
> I have confirmed that the request makes it to the localhost:3000, but with the upgrade header missing.
>
> I have also confirmed that the client request from the browser does have the upgrade header.
>
> I am wondering whether the use of a rewriterule with the “P” flag is the reason the upgrade header hasn’t been included.  I wonder this because it is pretty widely reported to work fine when using ProxyPass to ws uri.
>

I will have to look more later, but what's your exact httpd version? I
think if Upgrade is not preserved it implies mod_proxy_http was used
via either ProxyPass or some failure in mod_proxy_wstunnel taking over
-- so LogLevel trace8 might help.

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
For additional commands, e-mail: users-help@httpd.apache.org