Skip to content

Commit

Permalink
Add a buffered Tempfile setup
Browse files Browse the repository at this point in the history
similar to zip_tricks "next" branch
  • Loading branch information
julik committed Feb 3, 2024
1 parent 972d57c commit a441365
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 2 deletions.
9 changes: 7 additions & 2 deletions lib/zipline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
require "zipline/version"
require 'zip_tricks'
require "zipline/zip_generator"
require "zipline/chunked"
require "zipline/chunked_body"
require "zipline/tempfile_body"

# class MyController < ApplicationController
# include Zipline
Expand Down Expand Up @@ -31,7 +32,10 @@ def zipline(files, zipname = 'zipline.zip', **kwargs_for_new)

# Here it would be good natured to to read and save the ZIP into a tempfile, and serve it from there.
# Rack has a Rack::TempfileReaper middleware which could be used for that etc. Maybe one day.
self.response_body = zip_generator
tempfile_body = Zipline::TempfileBody.new(request.env, zip_generator)
headers["Content-Length"] = tempfile_body.size.to_s
headers["X-Zipline-Output"] = "buffered"
self.response_body = tempfile_body
else
# Disable buffering for both nginx and Google Load Balancer, see
# https://cloud.google.com/appengine/docs/flexible/how-requests-are-handled?tab=python#x-accel-buffering
Expand All @@ -43,6 +47,7 @@ def zipline(files, zipname = 'zipline.zip', **kwargs_for_new)

# and send out in chunked encoding
headers["Transfer-Encoding"] = "chunked"
headers["X-Zipline-Output"] = "streamed"
self.response_body = Zipline::Chunked.new(zip_generator)
end
end
Expand Down
File renamed without changes.
50 changes: 50 additions & 0 deletions lib/zipline/tempfile_body.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Contains a file handle which can be closed once the response finishes sending.
# It supports `to_path` so that `Rack::Sendfile` can intercept it
class Zipline::TempfileBody
TEMPFILE_NAME_PREFIX = "zipline-tf-body-"
attr_reader :tempfile

# @param body[#each] the enumerable that yields bytes, usually a `RackBody`.
# The `body` will be read in full immediately and closed.
def initialize(env, body)
@tempfile = Tempfile.new(TEMPFILE_NAME_PREFIX)
# Rack::TempfileReaper calls close! on tempfiles which get buffered
# We wil assume that it works fine with Rack::Sendfile (i.e. the path
# to the file getting served gets used before we unlink the tempfile)
env['rack.tempfiles'] ||= []
env['rack.tempfiles'] << @tempfile

@tempfile.binmode

body.each { |bytes| @tempfile << bytes }
body.close if body.respond_to?(:close)

@tempfile.flush
end

# Returns the size of the contained `Tempfile` so that a correct
# Content-Length header can be set
#
# @return [Integer]
def size
@tempfile.size
end

# Returns the path to the `Tempfile`, so that Rack::Sendfile can send this response
# using the downstream webserver
#
# @return [String]
def to_path
@tempfile.to_path
end

# Stream the file's contents if `Rack::Sendfile` isn't present.
#
# @return [void]
def each
@tempfile.rewind
while chunk = @tempfile.read(16384)
yield chunk
end
end
end

0 comments on commit a441365

Please sign in to comment.