socketry / async-websocket

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

WebSocket + Falcon + Rack middlewares using BodyProxy = No async task available! #34

Closed u3shit closed 2 years ago

u3shit commented 2 years ago

I'm trying to create a websocket server in a Rack application, unfortunately it looks like any middleware that uses BodyProxy (for example CommonLogger or Lint) break the websocket support.

require 'async/websocket/adapters/rack'
require 'rack'

app = lambda do |env|
  Async::WebSocket::Adapters::Rack.open(env) do |connection|
    message = connection.read
    connection.write message.reverse
  end
end

app = Rack::CommonLogger.new app
Rack::Handler.get('falcon').run app, Port: 9999

Trying to connect to this server results in:

 1.39s    error: Async::Task [oid=0x280] [ec=0x294] [pid=27023] [2021-12-22 17:40:57 +0100]
               |   ArgumentError: wrong number of arguments (given 1, expected 0)
               |   → /home/u3/.gem/ruby/3.0.0/gems/async-io-1.32.2/lib/async/io/stream.rb:216 in `close'
               |     /home/u3/.gem/ruby/3.0.0/gems/protocol-http-0.22.5/lib/protocol/http/body/readable.rb:75 in `ensure in call'
               |     /home/u3/.gem/ruby/3.0.0/gems/protocol-http-0.22.5/lib/protocol/http/body/readable.rb:75 in `call'
               |     /home/u3/.gem/ruby/3.0.0/gems/async-http-0.56.5/lib/async/http/protocol/http1/server.rb:82 in `each'
               |     /home/u3/.gem/ruby/3.0.0/gems/async-http-0.56.5/lib/async/http/server.rb:53 in `accept'
               |     /home/u3/.gem/ruby/3.0.0/gems/async-io-1.32.2/lib/async/io/server.rb:32 in `block in accept_each'
               |     /home/u3/.gem/ruby/3.0.0/gems/async-io-1.32.2/lib/async/io/socket.rb:73 in `block in accept'
               |     /home/u3/.gem/ruby/3.0.0/gems/async-1.30.1/lib/async/task.rb:260 in `block in make_fiber'
               |   Caused by RuntimeError: No async task available!
               |   → /home/u3/.gem/ruby/3.0.0/gems/async-1.30.1/lib/async/task.rb:189 in `current'
               |     /home/u3/.gem/ruby/3.0.0/gems/async-http-0.56.5/lib/async/http/body/hijack.rb:78 in `read'
               |     /home/u3/.gem/ruby/3.0.0/gems/protocol-http-0.22.5/lib/protocol/http/body/readable.rb:86 in `each'
               |     /home/u3/.gem/ruby/3.0.0/gems/rack-2.2.3/lib/rack/body_proxy.rb:41 in `method_missing'
               |     test.rb:in `each' 

This does not happen if I remove the app = Rack::CommonLogger.new app line.

Falcon::Adapter::Output does a @body.to_enum(:each) which creates a Fiber internally, but it won't be an async fiber, hence the error. (Without BodyProxy, falcon can somehow figure out the response is a Hijack and it doesn't even create an Output from what can I tell.) Any idea what to do with this? (I'm trying to port an app using EventMachine+thin+faye-websocket, there this configuration worked without problems.) P.s. the rack server example doesn't seem to work for me, it throws an undefined method `run' for main:Object error in an infinite loop for me.

ioquatix commented 2 years ago

I believe Rack 3.0 will fix this problem, but let me investigate.

ioquatix commented 2 years ago

We have merged the streaming proposal so Rack 3 will work correctly, even from within Rails, but it will take some time for this change to work it's way through the eco-system.