Last active
May 8, 2021 06:58
-
-
Save janko/1e7676abf81e88c5c8ab6f77af58b14b to your computer and use it in GitHub Desktop.
Simpler implementation of ActionController::Live
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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