socketry / async-websocket

Asynchronous WebSocket client and server, supporting HTTP/1 and HTTP/2 for Ruby.
MIT License
166 stars 18 forks source link

Hangs when connecting to nginx proxied websocket server using HTTP2 #65

Open u3shit opened 8 months ago

u3shit commented 8 months ago

I did what I shouldn't do and updated everything at one time again, and now I can no longer connect to a websocket server behind an nginx reverse proxy.

A simple program like this

require 'async/http/endpoint'
require 'async/websocket/client'

Console.logger.level = :debug

ep = Async::HTTP::Endpoint.parse 'wss://some-host/'
Async do
  Async::WebSocket::Client.connect ep do |conn|
    puts conn
  end
end

Now fails (removed IP addresses):

/home/u3/.gem/ruby/3.2.0/gems/io-event-1.3.3/lib/io/event/support.rb:24: warning: IO::Buffer is experimental and both the Ruby and C interface may change in the future!
  0.0s    debug: Async::Pool::Controller [oid=0x26c] [ec=0x280] [pid=21735] [2023-12-17 21:11:42 +0100]
               | No available resources, allocating new one...
  0.0s    debug: Async::HTTP::Client [oid=0x2a8] [ec=0x280] [pid=21735] [2023-12-17 21:11:42 +0100]
               | Making connection to #<Async::HTTP::Endpoint wss://xxxx {}>
  0.0s    debug: Async::IO::Socket [ec=0x280] [pid=21735] [2023-12-17 21:11:42 +0100]
               | Connecting to #<Addrinfo: xxx.xxx.xxx.xxx:443 TCP (xxxx)>
 0.07s    debug: Async::HTTP::Protocol::HTTPS: connected to #<Addrinfo: xxx.xxx.xxx.xxx:443 TCP (xxxx)> [fd=6] [ec=0x280] [pid=21735] [2023-12-17 21:11:43 +0100]
               | Negotiating protocol "h2"...
  0.1s    debug: Async::Pool::Controller: connected to #<Addrinfo: xxx.xxx.xxx.xxx:443 TCP (xxxx)> [fd=6] [oid=0x26c] [ec=0x280] [pid=21735] [2023-12-17 21:11:43 +0100]
               | Wait for resource -> #<Async::HTTP::Protocol::HTTP2::Client 0 active streams>

And it just hangs there until I Ctrl-C it. Nginx sends back a HTTP 400 error page (verified with wireshark), but this lib doesn't seem to handle it. Additionally nginx logs a client sent unknown pseudo-header ":protocol" while reading client request headers error, so I think it doesn't support websocket over HTTP2. (The older version I upgraded from used HTTP/1.1 so it worked fine there).

The alpn_protocols: Async::HTTP::Protocol::HTTP11.names workaround fixes the problem, but I think it should at least error out on the 400 response, instead of hanging the coroutine.

u3shit commented 8 months ago

Sorry, I haven't noticed you added a CoC in the meantime.

ioquatix commented 8 months ago

Probably the correct way to handle this is to check whether the HTTP/2 connection advertises support for the connect protocol, and if not, fall back to HTTP/1 for WebSockets.