Skip to content

Instantly share code, notes, and snippets.

@janko
Last active May 8, 2021 06:58
Show Gist options
  • Save janko/1e7676abf81e88c5c8ab6f77af58b14b to your computer and use it in GitHub Desktop.
Save janko/1e7676abf81e88c5c8ab6f77af58b14b to your computer and use it in GitHub Desktop.
Simpler implementation of ActionController::Live
class ApplicationController < ActionController::Base
private
def stream_body(async: false, **options, &block)
self.status ||= 200
self.headers["Cache-Control"] = "no-cache"
self.headers.delete("Content-Length")
stream_class = async ? AsyncStream : Stream
self.response_body = stream_class.new(**options, &block)
end
class AsyncStream
def initialize(queue: SizedQueue.new(10), **options, &block)
@stream = Stream.new(**options, &block)
@queue = queue
end
def each
Thread.new do
Thread.current.abort_on_exception = true
@stream.each { |chunk| @queue.push(chunk) }
@queue.close
rescue ClosedQueueError
# download request terminated early, we let the thread finish
end
while (chunk = @queue.pop)
yield chunk
end
end
def close
@queue.close
end
end
class Stream
def initialize(**, &block)
@block = block
end
def each(&chunk)
writer = Writer.new(&chunk)
@block.call(writer)
end
class Writer
def initialize(&chunk)
@chunk = chunk
end
def write(data)
@chunk.call(data)
data.bytesize # makes it work with IO.copy_stream
end
alias << write
end
end
end
class FilesController < ApplicationController
def download
self.headers.merge!(
"Content-Disposition" => ContentDisposition.attachment("file-streaming.txt"),
"Content-Type" => "text/plain",
"Last-Modified" => Time.now.httpdate,
)
stream_body(async: true) do |out|
sleep 1 # mimic delay
out << "foo"
sleep 1 # mimic delay
out << "bar"
sleep 1 # mimic delay
out << "baz"
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment