-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #95 from julik/add-explicit-chunking
Apply explicit chunked encoding
- Loading branch information
Showing
9 changed files
with
164 additions
and
160 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
module Zipline | ||
# A body wrapper that emits chunked responses, creating valid | ||
# "Transfer-Encoding: chunked" HTTP response body. This is copied from Rack::Chunked::Body, | ||
# because Rack is not going to include that class after version 3.x | ||
# Rails has a substitute class for this inside ActionController::Streaming, | ||
# but that module is a private constant in the Rails codebase, and is thus | ||
# considered "private" from the Rails standpoint. It is not that much code to | ||
# carry, so we copy it into our code. | ||
class Chunked | ||
TERM = "\r\n" | ||
TAIL = "0#{TERM}" | ||
|
||
def initialize(body) | ||
@body = body | ||
end | ||
|
||
# For each string yielded by the response body, yield | ||
# the element in chunked encoding - and finish off with a terminator | ||
def each | ||
term = TERM | ||
@body.each do |chunk| | ||
size = chunk.bytesize | ||
next if size == 0 | ||
|
||
yield [size.to_s(16), term, chunk.b, term].join | ||
end | ||
yield TAIL | ||
yield term | ||
end | ||
end | ||
end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
module Zipline | ||
# 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 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 | ||
end |
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,34 @@ | ||
require 'spec_helper' | ||
require 'ostruct' | ||
require 'action_controller' | ||
|
||
describe Zipline do | ||
before { Fog.mock! } | ||
|
||
let (:undertest) { | ||
class TestZipline | ||
class FakeController < ActionController::Base | ||
include Zipline | ||
def download_zip | ||
files = [ | ||
[StringIO.new("File content goes here"), "one.txt"], | ||
[StringIO.new("Some other content goes here"), "two.txt"] | ||
] | ||
zipline(files, 'myfiles.zip', auto_rename_duplicate_filenames: false) | ||
end | ||
end | ||
|
||
attr_accessor :headers | ||
attr_accessor :response | ||
attr_accessor :response_body | ||
def initialize | ||
@headers = {} | ||
@response = OpenStruct.new(:cache_control => {}, :headers => {} ) | ||
end | ||
include Zipline | ||
end | ||
return TestZipline.new() | ||
} | ||
it 'passes keyword parameters to ZipTricks::Streamer' do | ||
fake_rack_env = { | ||
"HTTP_VERSION" => "HTTP/1.0", | ||
"REQUEST_METHOD" => "GET", | ||
"SCRIPT_NAME" => "", | ||
"PATH_INFO" => "/download", | ||
"QUERY_STRING" => "", | ||
"SERVER_NAME" => "host.example", | ||
"rack.input" => StringIO.new, | ||
} | ||
expect(ZipTricks::Streamer).to receive(:new).with(anything, auto_rename_duplicate_filenames: false).and_call_original | ||
|
||
status, headers, body = FakeController.action(:download_zip).call(fake_rack_env) | ||
|
||
it 'passes arguments along' do | ||
expect(Zipline::ZipGenerator).to receive(:new) | ||
.with(['some', 'fake', 'files'], { some: 'options' }) | ||
undertest.zipline(['some', 'fake', 'files'], 'myfiles.zip', some: 'options') | ||
expect(undertest.headers['Content-Disposition']).to eq("attachment; filename=\"myfiles.zip\"; filename*=UTF-8''myfiles.zip") | ||
expect(headers['Content-Disposition']).to eq("attachment; filename=\"myfiles.zip\"; filename*=UTF-8''myfiles.zip") | ||
end | ||
end |
This file contains 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
This file contains 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