Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

streaming responses #43

Closed
matti opened this issue Jan 10, 2019 · 6 comments
Closed

streaming responses #43

matti opened this issue Jan 10, 2019 · 6 comments

Comments

@matti
Copy link

matti commented Jan 10, 2019

I tried to adapt code from beer.rb and run falcon from my ruby and not with falcon - can't get streaming responses to work, the following outputs just one "hello" before connection is closed:

class Chunk
  def call(env)
    task = Async::Task.current
    body = Async::HTTP::Body::Writable.new
    task.async do |task|
      10.times do
        body.write "hello"
        task.sleep 0.5
      end
    rescue Exception => ex
      p ex
    ensure
      body.close
    end

    [200 {'content-type' => 'text/html; charset=utf-8'}, body]
  end
end
rack_app = Rack::Builder.new do
  use Rack::CommonLogger

  map "/chunk" do
    run Chunk.new
  end
end

app = Falcon::Server.middleware rack_app, verbose: true

endpoint = Async::HTTP::URLEndpoint.parse "http://0.0.0.0:1234"
bound_endpoint = Async::Reactor.run do
  Async::IO::SharedEndpoint.bound(endpoint)
end.result

server = Falcon::Server.new(app, bound_endpoint, endpoint.protocol, endpoint.scheme)

Async::Reactor.run do
  server.run
  puts "serving..."
end
@ioquatix
Copy link
Member

use Rack::CommonLogger is not compatible with streaming body it seems.

@ioquatix
Copy link
Member

ioquatix commented Jan 10, 2019

Here is implementation:

https://github.com/rack/rack/blob/master/lib/rack/common_logger.rb

It's using BodyProxy, so the code here is not triggered:

if body.is_a?(Async::HTTP::Body::Readable)
return body

Instead it goes through this code path:

return self.new(headers, body, content_length)

This makes an enumerator over the response body:

@chunks = body.to_enum(:each)

But this is not well supported in Ruby at the moment for async tasks. Therefore, it fails in a strange way. Unfortunately the best solution right now is to avoid using Rack::CommonLogger or for us to some how special case this.

@ioquatix
Copy link
Member

Here is a working example:

#!/usr/bin/env ruby

require 'async'
require 'async/http/client'
require 'async/http/url_endpoint'
require 'async/io/shared_endpoint'
require 'falcon/server'

# Async.logger.level = Logger::DEBUG

class Chunk
	def call(env)
		task = Async::Task.current
		body = Async::HTTP::Body::Writable.new
		task.async do |task|
			10.times do
				body.write "hello"
				task.sleep 0.5
			end
		rescue Exception => ex
			p ex
		ensure
			body.close
		end
		
		[200, {'content-type' => 'text/html; charset=utf-8'}, body]
	end
end
rack_app = Rack::Builder.new do
	#use Rack::CommonLogger

	map "/chunk" do
		run Chunk.new
	end
end

app = Falcon::Server.middleware rack_app, verbose: true

endpoint = Async::HTTP::URLEndpoint.parse "http://0.0.0.0:12345"

bound_endpoint = Async::Reactor.run do
	Async::IO::SharedEndpoint.bound(endpoint)
end.result

server = Falcon::Server.new(app, bound_endpoint, endpoint.protocol, endpoint.scheme)

Async.run do |task|
	server_task = task.async do
		puts "Starting server..."
		server.run
	end
	
	client = Async::HTTP::Client.new(endpoint)
	
	puts "Fetching /chunk"
	response = client.get("/chunk")
	
	response.body.each do |chunk|
		puts chunk
	end
end

@matti
Copy link
Author

matti commented Jan 10, 2019

@ioquatix thank you again!

@ioquatix
Copy link
Member

The only real solution I can see here is to fix enumerators: ruby/ruby#2002

@matti
Copy link
Author

matti commented Jan 14, 2019

I think issue can be closed - the example works great.

@matti matti closed this as completed Jan 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants