zipline 1.6.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/zipline/version.rb +1 -1
- data/lib/zipline/{zip_generator.rb → zip_handler.rb} +13 -39
- data/lib/zipline.rb +12 -39
- data/spec/lib/zipline/{zip_generator_spec.rb → zip_handler_spec.rb} +14 -14
- data/spec/lib/zipline/zipline_spec.rb +12 -7
- data/zipline.gemspec +1 -1
- metadata +16 -12
- data/lib/zipline/chunked_body.rb +0 -31
- data/lib/zipline/tempfile_body.rb +0 -52
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 954c769aaf6aa7780a8770fcc9991a1beea5b326f1920b6bfdcc921dcef1a34d
|
4
|
+
data.tar.gz: 7e40d818c5b4e7da8c8167f42e9318d0d7cfee2384657a84fa3ee294184f9819
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a39143123fc75b44b94d88ca0e8d31a41e2b63f9368ff70692c502c21171baf406e4eb8bb7a9f9ef0885d2883d8c5e5d40995499068a2a0a9a9906c9135bc68
|
7
|
+
data.tar.gz: 84c7b320023d7195f619638b87acaa14121852a643a727a9ba5c1d848ed3d4bc045832347586ca3b454dfb753ad189f2bfe96d5e97bd6140065a1fa0ef4675b8
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ A gem to stream dynamically generated zip files from a rails application. Unlike
|
|
7
7
|
- Removes need for large disk space or memory allocation to generate zips, even huge zips. So it works on Heroku.
|
8
8
|
- The user begins downloading immediately, which decreaceses latency, download time, and timeouts on Heroku.
|
9
9
|
|
10
|
-
Zipline now depends on [
|
10
|
+
Zipline now depends on [zip_kit](https://github.com/julik/zip_kit), and you might want to just use that directly if you have more advanced use cases.
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
@@ -43,7 +43,7 @@ class MyController < ApplicationController
|
|
43
43
|
files = users.map{ |user| [user.avatar, "#{user.username}.png", modification_time: 1.day.ago] }
|
44
44
|
|
45
45
|
# we can force duplicate file names to be renamed, or raise an error
|
46
|
-
# we can also pass in our own writer if required to conform with the
|
46
|
+
# we can also pass in our own writer if required to conform with the delegated [ZipKit::Streamer object](https://github.com/julik/zip_kit/blob/main/lib/zip_kit/streamer.rb#L147) object.
|
47
47
|
zipline(files, 'avatars.zip', auto_rename_duplicate_filenames: true)
|
48
48
|
end
|
49
49
|
end
|
@@ -93,7 +93,7 @@ For directories, just give the files names like "directory/file".
|
|
93
93
|
|
94
94
|
```Ruby
|
95
95
|
avatars = [
|
96
|
-
# remote_url zip_path
|
96
|
+
# remote_url zip_path write_file options for Streamer
|
97
97
|
[ 'http://www.example.com/user1.png', 'avatars/user1.png', modification_time: Time.now.utc ]
|
98
98
|
[ 'http://www.example.com/user2.png', 'avatars/user2.png', modification_time: 1.day.ago ]
|
99
99
|
[ 'http://www.example.com/user3.png', 'avatars/user3.png' ]
|
data/lib/zipline/version.rb
CHANGED
@@ -1,39 +1,23 @@
|
|
1
|
-
# this class acts as a streaming body for rails
|
2
|
-
# initialize it with an array of the files you want to zip
|
3
1
|
module Zipline
|
4
|
-
class
|
2
|
+
class ZipHandler
|
5
3
|
# takes an array of pairs [[uploader, filename], ... ]
|
6
|
-
def initialize(
|
7
|
-
|
8
|
-
@
|
9
|
-
files.each do |file, name, options = {}|
|
10
|
-
handle_file(streamer, file, name.to_s, options)
|
11
|
-
end
|
12
|
-
end
|
4
|
+
def initialize(streamer, logger)
|
5
|
+
@streamer = streamer
|
6
|
+
@logger = logger
|
13
7
|
end
|
14
8
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
9
|
+
def handle_file(file, name, options)
|
10
|
+
normalized_file = normalize(file)
|
11
|
+
write_file(normalized_file, name, options)
|
18
12
|
rescue => e
|
19
13
|
# Since most APM packages do not trace errors occurring within streaming
|
20
14
|
# Rack bodies, it can be helpful to print the error to the Rails log at least
|
21
15
|
error_message = "zipline: an exception (#{e.inspect}) was raised when serving the ZIP body."
|
22
|
-
error_message += " The error occurred when handling #{
|
23
|
-
logger.error(error_message)
|
16
|
+
error_message += " The error occurred when handling file #{name.inspect}"
|
17
|
+
@logger.error(error_message) if @logger
|
24
18
|
raise
|
25
19
|
end
|
26
20
|
|
27
|
-
def handle_file(streamer, file, name, options)
|
28
|
-
file = normalize(file)
|
29
|
-
|
30
|
-
# Store the filename so that a sensible error message can be displayed in the log
|
31
|
-
# if writing this particular file fails
|
32
|
-
@filename = name
|
33
|
-
write_file(streamer, file, name, options)
|
34
|
-
@filename = nil
|
35
|
-
end
|
36
|
-
|
37
21
|
# This extracts either a url or a local file from the provided file.
|
38
22
|
# Currently support carrierwave and paperclip local and remote storage.
|
39
23
|
# returns a hash of the form {url: aUrl} or {file: anIoObject}
|
@@ -71,8 +55,8 @@ module Zipline
|
|
71
55
|
end
|
72
56
|
end
|
73
57
|
|
74
|
-
def write_file(
|
75
|
-
streamer.
|
58
|
+
def write_file(file, name, options)
|
59
|
+
@streamer.write_file(name, **options.slice(:modification_time)) do |writer_for_file|
|
76
60
|
if file[:url]
|
77
61
|
the_remote_uri = URI(file[:url])
|
78
62
|
|
@@ -92,20 +76,10 @@ module Zipline
|
|
92
76
|
end
|
93
77
|
end
|
94
78
|
|
95
|
-
def is_io?(io_ish)
|
96
|
-
io_ish.respond_to? :read
|
97
|
-
end
|
98
|
-
|
99
79
|
private
|
100
80
|
|
101
|
-
def
|
102
|
-
|
103
|
-
# elsewhere - or the logger might not be configured correctly
|
104
|
-
if defined?(Rails.logger) && Rails.logger
|
105
|
-
Rails.logger
|
106
|
-
else
|
107
|
-
Logger.new($stderr)
|
108
|
-
end
|
81
|
+
def is_io?(io_ish)
|
82
|
+
io_ish.respond_to? :read
|
109
83
|
end
|
110
84
|
|
111
85
|
def is_active_storage_attachment?(file)
|
data/lib/zipline.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
require 'content_disposition'
|
2
|
+
require 'zip_kit'
|
2
3
|
require 'zipline/version'
|
3
|
-
require '
|
4
|
-
require 'zipline/zip_generator'
|
5
|
-
require 'zipline/chunked_body'
|
6
|
-
require 'zipline/tempfile_body'
|
4
|
+
require 'zipline/zip_handler'
|
7
5
|
|
8
6
|
# class MyController < ApplicationController
|
9
7
|
# include Zipline
|
@@ -14,42 +12,17 @@ require 'zipline/tempfile_body'
|
|
14
12
|
# end
|
15
13
|
# end
|
16
14
|
module Zipline
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
response.sending_file = true
|
22
|
-
response.cache_control[:public] ||= false
|
23
|
-
|
24
|
-
# Disables Rack::ETag if it is enabled (prevent buffering)
|
25
|
-
# see https://github.com/rack/rack/issues/1619#issuecomment-606315714
|
26
|
-
self.response.headers['Last-Modified'] = Time.now.httpdate
|
27
|
-
|
28
|
-
if request.get_header("HTTP_VERSION") == "HTTP/1.0"
|
29
|
-
# If HTTP/1.0 is used it is not possible to stream, and if that happens it usually will be
|
30
|
-
# unclear why buffering is happening. Some info in the log is the least one can do.
|
31
|
-
logger.warn { "The downstream HTTP proxy/LB insists on HTTP/1.0 protocol, ZIP response will be buffered." } if logger
|
32
|
-
|
33
|
-
# If we use Rack::ContentLength it would run through our ZIP block twice - once to calculate the content length
|
34
|
-
# of the response, and once - to serve. We can trade performance for disk space and buffer the response into a Tempfile
|
35
|
-
# since we are already buffering.
|
36
|
-
tempfile_body = TempfileBody.new(request.env, zip_generator)
|
37
|
-
headers["Content-Length"] = tempfile_body.size.to_s
|
38
|
-
headers["X-Zipline-Output"] = "buffered"
|
39
|
-
self.response_body = tempfile_body
|
40
|
-
else
|
41
|
-
# Disable buffering for both nginx and Google Load Balancer, see
|
42
|
-
# https://cloud.google.com/appengine/docs/flexible/how-requests-are-handled?tab=python#x-accel-buffering
|
43
|
-
response.headers["X-Accel-Buffering"] = "no"
|
44
|
-
|
45
|
-
# Make sure Rack::ContentLength does not try to compute a content length,
|
46
|
-
# and remove the one already set
|
47
|
-
headers.delete("Content-Length")
|
15
|
+
def self.included(into_controller)
|
16
|
+
into_controller.include(ZipKit::RailsStreaming)
|
17
|
+
super
|
18
|
+
end
|
48
19
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
20
|
+
def zipline(files, zipname = 'zipline.zip', **kwargs_for_zip_kit_stream)
|
21
|
+
zip_kit_stream(filename: zipname, **kwargs_for_zip_kit_stream) do |zip_kit_streamer|
|
22
|
+
handler = Zipline::ZipHandler.new(zip_kit_streamer, logger)
|
23
|
+
files.each do |file, name, options = {}|
|
24
|
+
handler.handle_file(file, name.to_s, options)
|
25
|
+
end
|
53
26
|
end
|
54
27
|
end
|
55
28
|
end
|
@@ -18,7 +18,7 @@ module ActiveStorage
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
describe Zipline::
|
21
|
+
describe Zipline::ZipHandler do
|
22
22
|
before { Fog.mock! }
|
23
23
|
let(:file_attributes){ {
|
24
24
|
key: 'fog_file_tests',
|
@@ -38,21 +38,21 @@ describe Zipline::ZipGenerator do
|
|
38
38
|
let(:file){ directory.files.create(file_attributes) }
|
39
39
|
|
40
40
|
describe '.normalize' do
|
41
|
-
let(:
|
41
|
+
let(:handler){ Zipline::ZipHandler.new(_streamer = double(), _logger = nil)}
|
42
42
|
context "CarrierWave" do
|
43
43
|
context "Remote" do
|
44
44
|
let(:file){ CarrierWave::Storage::Fog::File.new(nil,nil,nil) }
|
45
45
|
it "extracts the url" do
|
46
46
|
allow(file).to receive(:url).and_return('fakeurl')
|
47
47
|
expect(File).not_to receive(:open)
|
48
|
-
expect(
|
48
|
+
expect(handler.normalize(file)).to eq({url: 'fakeurl'})
|
49
49
|
end
|
50
50
|
end
|
51
51
|
context "Local" do
|
52
52
|
let(:file){ CarrierWave::SanitizedFile.new(Tempfile.new('t')) }
|
53
53
|
it "creates a File" do
|
54
54
|
allow(file).to receive(:path).and_return('spec/fakefile.txt')
|
55
|
-
normalized =
|
55
|
+
normalized = handler.normalize(file)
|
56
56
|
expect(normalized.keys).to include(:file)
|
57
57
|
expect(normalized[:file]).to be_a File
|
58
58
|
end
|
@@ -66,7 +66,7 @@ describe Zipline::ZipGenerator do
|
|
66
66
|
allow(uploader).to receive(:file).and_return(file)
|
67
67
|
allow(file).to receive(:url).and_return('fakeurl')
|
68
68
|
expect(File).not_to receive(:open)
|
69
|
-
expect(
|
69
|
+
expect(handler.normalize(uploader)).to eq({url: 'fakeurl'})
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -75,7 +75,7 @@ describe Zipline::ZipGenerator do
|
|
75
75
|
it "creates a File" do
|
76
76
|
allow(uploader).to receive(:file).and_return(file)
|
77
77
|
allow(file).to receive(:path).and_return('spec/fakefile.txt')
|
78
|
-
normalized =
|
78
|
+
normalized = handler.normalize(uploader)
|
79
79
|
expect(normalized.keys).to include(:file)
|
80
80
|
expect(normalized[:file]).to be_a File
|
81
81
|
end
|
@@ -87,7 +87,7 @@ describe Zipline::ZipGenerator do
|
|
87
87
|
let(:file){ Paperclip::Attachment.new(:name, :instance) }
|
88
88
|
it "creates a File" do
|
89
89
|
allow(file).to receive(:path).and_return('spec/fakefile.txt')
|
90
|
-
normalized =
|
90
|
+
normalized = handler.normalize(file)
|
91
91
|
expect(normalized.keys).to include(:file)
|
92
92
|
expect(normalized[:file]).to be_a File
|
93
93
|
end
|
@@ -97,7 +97,7 @@ describe Zipline::ZipGenerator do
|
|
97
97
|
it "creates a URL" do
|
98
98
|
allow(file).to receive(:expiring_url).and_return('fakeurl')
|
99
99
|
expect(File).to_not receive(:open)
|
100
|
-
expect(
|
100
|
+
expect(handler.normalize(file)).to include(url: 'fakeurl')
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
@@ -107,7 +107,7 @@ describe Zipline::ZipGenerator do
|
|
107
107
|
attached = create_attached_one
|
108
108
|
allow_any_instance_of(Object).to receive(:defined?).and_return(true)
|
109
109
|
|
110
|
-
normalized =
|
110
|
+
normalized = handler.normalize(attached)
|
111
111
|
|
112
112
|
expect(normalized.keys).to include(:blob)
|
113
113
|
expect(normalized[:blob]).to be_a(ActiveStorage::Blob)
|
@@ -119,7 +119,7 @@ describe Zipline::ZipGenerator do
|
|
119
119
|
attachment = create_attachment
|
120
120
|
allow_any_instance_of(Object).to receive(:defined?).and_return(true)
|
121
121
|
|
122
|
-
normalized =
|
122
|
+
normalized = handler.normalize(attachment)
|
123
123
|
|
124
124
|
expect(normalized.keys).to include(:blob)
|
125
125
|
expect(normalized[:blob]).to be_a(ActiveStorage::Blob)
|
@@ -131,7 +131,7 @@ describe Zipline::ZipGenerator do
|
|
131
131
|
blob = create_blob
|
132
132
|
allow_any_instance_of(Object).to receive(:defined?).and_return(true)
|
133
133
|
|
134
|
-
normalized =
|
134
|
+
normalized = handler.normalize(blob)
|
135
135
|
|
136
136
|
expect(normalized.keys).to include(:blob)
|
137
137
|
expect(normalized[:blob]).to be_a(ActiveStorage::Blob)
|
@@ -169,19 +169,19 @@ describe Zipline::ZipGenerator do
|
|
169
169
|
it "extracts url" do
|
170
170
|
allow(file).to receive(:url).and_return('fakeurl')
|
171
171
|
expect(File).not_to receive(:open)
|
172
|
-
expect(
|
172
|
+
expect(handler.normalize(file)).to eq(url: 'fakeurl')
|
173
173
|
end
|
174
174
|
end
|
175
175
|
context "IOStream" do
|
176
176
|
let(:file){ StringIO.new('passthrough')}
|
177
177
|
it "passes through" do
|
178
|
-
expect(
|
178
|
+
expect(handler.normalize(file)).to eq(file: file)
|
179
179
|
end
|
180
180
|
end
|
181
181
|
context "invalid" do
|
182
182
|
let(:file){ Thread.new{} }
|
183
183
|
it "raises error" do
|
184
|
-
expect{
|
184
|
+
expect{handler.normalize(file)}.to raise_error(ArgumentError)
|
185
185
|
end
|
186
186
|
end
|
187
187
|
end
|
@@ -2,7 +2,10 @@ require 'spec_helper'
|
|
2
2
|
require 'action_controller'
|
3
3
|
|
4
4
|
describe Zipline do
|
5
|
-
before
|
5
|
+
before do
|
6
|
+
Fog.mock!
|
7
|
+
FakeController.logger = nil
|
8
|
+
end
|
6
9
|
|
7
10
|
class FakeController < ActionController::Base
|
8
11
|
include Zipline
|
@@ -29,7 +32,7 @@ describe Zipline do
|
|
29
32
|
end
|
30
33
|
end
|
31
34
|
|
32
|
-
it 'passes keyword parameters to
|
35
|
+
it 'passes keyword parameters to ZipKit::OutputEnumerator' do
|
33
36
|
fake_rack_env = {
|
34
37
|
"HTTP_VERSION" => "HTTP/1.0",
|
35
38
|
"REQUEST_METHOD" => "GET",
|
@@ -39,7 +42,7 @@ describe Zipline do
|
|
39
42
|
"SERVER_NAME" => "host.example",
|
40
43
|
"rack.input" => StringIO.new,
|
41
44
|
}
|
42
|
-
expect(
|
45
|
+
expect(ZipKit::OutputEnumerator).to receive(:new).with(auto_rename_duplicate_filenames: false).and_call_original
|
43
46
|
|
44
47
|
status, headers, body = FakeController.action(:download_zip).call(fake_rack_env)
|
45
48
|
|
@@ -56,13 +59,15 @@ describe Zipline do
|
|
56
59
|
"SERVER_NAME" => "host.example",
|
57
60
|
"rack.input" => StringIO.new,
|
58
61
|
}
|
59
|
-
expect(ZipTricks::Streamer).to receive(:new).with(anything, auto_rename_duplicate_filenames: false).and_call_original
|
60
62
|
fake_logger = double()
|
61
|
-
|
62
|
-
expect(fake_logger).to receive(:error).with(
|
63
|
+
allow(fake_logger).to receive(:warn)
|
64
|
+
expect(fake_logger).to receive(:error).with(a_string_matching(/when serving the ZIP/))
|
65
|
+
|
66
|
+
FakeController.logger = fake_logger
|
63
67
|
|
64
68
|
expect {
|
65
|
-
FakeController.action(:download_zip_with_error_during_streaming).call(fake_rack_env)
|
69
|
+
status, headers, body = FakeController.action(:download_zip_with_error_during_streaming).call(fake_rack_env)
|
70
|
+
body.each { }
|
66
71
|
}.to raise_error(/Something wonky/)
|
67
72
|
end
|
68
73
|
end
|
data/zipline.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
|
|
20
20
|
|
21
21
|
gem.add_dependency 'actionpack', ['>= 6.0', '< 8.0']
|
22
22
|
gem.add_dependency 'content_disposition', '~> 1.0'
|
23
|
-
gem.add_dependency '
|
23
|
+
gem.add_dependency 'zip_kit', ['~> 6', '>= 6.2.0', '< 7']
|
24
24
|
|
25
25
|
gem.add_development_dependency 'rspec', '~> 3'
|
26
26
|
gem.add_development_dependency 'fog-aws'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zipline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ram Dobson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-03-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -45,25 +45,31 @@ dependencies:
|
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: '1.0'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
|
-
name:
|
48
|
+
name: zip_kit
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: '
|
53
|
+
version: '6'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 6.2.0
|
54
57
|
- - "<"
|
55
58
|
- !ruby/object:Gem::Version
|
56
|
-
version: '
|
59
|
+
version: '7'
|
57
60
|
type: :runtime
|
58
61
|
prerelease: false
|
59
62
|
version_requirements: !ruby/object:Gem::Requirement
|
60
63
|
requirements:
|
61
64
|
- - "~>"
|
62
65
|
- !ruby/object:Gem::Version
|
63
|
-
version: '
|
66
|
+
version: '6'
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 6.2.0
|
64
70
|
- - "<"
|
65
71
|
- !ruby/object:Gem::Version
|
66
|
-
version: '
|
72
|
+
version: '7'
|
67
73
|
- !ruby/object:Gem::Dependency
|
68
74
|
name: rspec
|
69
75
|
requirement: !ruby/object:Gem::Requirement
|
@@ -175,12 +181,10 @@ files:
|
|
175
181
|
- README.md
|
176
182
|
- Rakefile
|
177
183
|
- lib/zipline.rb
|
178
|
-
- lib/zipline/chunked_body.rb
|
179
|
-
- lib/zipline/tempfile_body.rb
|
180
184
|
- lib/zipline/version.rb
|
181
|
-
- lib/zipline/
|
185
|
+
- lib/zipline/zip_handler.rb
|
182
186
|
- spec/fakefile.txt
|
183
|
-
- spec/lib/zipline/
|
187
|
+
- spec/lib/zipline/zip_handler_spec.rb
|
184
188
|
- spec/lib/zipline/zipline_spec.rb
|
185
189
|
- spec/spec_helper.rb
|
186
190
|
- zipline.gemspec
|
@@ -209,6 +213,6 @@ specification_version: 4
|
|
209
213
|
summary: stream zip files from rails
|
210
214
|
test_files:
|
211
215
|
- spec/fakefile.txt
|
212
|
-
- spec/lib/zipline/
|
216
|
+
- spec/lib/zipline/zip_handler_spec.rb
|
213
217
|
- spec/lib/zipline/zipline_spec.rb
|
214
218
|
- spec/spec_helper.rb
|
data/lib/zipline/chunked_body.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
module Zipline
|
2
|
-
# A body wrapper that emits chunked responses, creating valid
|
3
|
-
# "Transfer-Encoding: chunked" HTTP response body. This is copied from Rack::Chunked::Body,
|
4
|
-
# because Rack is not going to include that class after version 3.x
|
5
|
-
# Rails has a substitute class for this inside ActionController::Streaming,
|
6
|
-
# but that module is a private constant in the Rails codebase, and is thus
|
7
|
-
# considered "private" from the Rails standpoint. It is not that much code to
|
8
|
-
# carry, so we copy it into our code.
|
9
|
-
class Chunked
|
10
|
-
TERM = "\r\n"
|
11
|
-
TAIL = "0#{TERM}"
|
12
|
-
|
13
|
-
def initialize(body)
|
14
|
-
@body = body
|
15
|
-
end
|
16
|
-
|
17
|
-
# For each string yielded by the response body, yield
|
18
|
-
# the element in chunked encoding - and finish off with a terminator
|
19
|
-
def each
|
20
|
-
term = TERM
|
21
|
-
@body.each do |chunk|
|
22
|
-
size = chunk.bytesize
|
23
|
-
next if size == 0
|
24
|
-
|
25
|
-
yield [size.to_s(16), term, chunk.b, term].join
|
26
|
-
end
|
27
|
-
yield TAIL
|
28
|
-
yield term
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
module Zipline
|
2
|
-
# Contains a file handle which can be closed once the response finishes sending.
|
3
|
-
# It supports `to_path` so that `Rack::Sendfile` can intercept it
|
4
|
-
class TempfileBody
|
5
|
-
TEMPFILE_NAME_PREFIX = "zipline-tf-body-"
|
6
|
-
attr_reader :tempfile
|
7
|
-
|
8
|
-
# @param body[#each] the enumerable that yields bytes, usually a `RackBody`.
|
9
|
-
# The `body` will be read in full immediately and closed.
|
10
|
-
def initialize(env, body)
|
11
|
-
@tempfile = Tempfile.new(TEMPFILE_NAME_PREFIX)
|
12
|
-
# Rack::TempfileReaper calls close! on tempfiles which get buffered
|
13
|
-
# We wil assume that it works fine with Rack::Sendfile (i.e. the path
|
14
|
-
# to the file getting served gets used before we unlink the tempfile)
|
15
|
-
env['rack.tempfiles'] ||= []
|
16
|
-
env['rack.tempfiles'] << @tempfile
|
17
|
-
|
18
|
-
@tempfile.binmode
|
19
|
-
|
20
|
-
body.each { |bytes| @tempfile << bytes }
|
21
|
-
body.close if body.respond_to?(:close)
|
22
|
-
|
23
|
-
@tempfile.flush
|
24
|
-
end
|
25
|
-
|
26
|
-
# Returns the size of the contained `Tempfile` so that a correct
|
27
|
-
# Content-Length header can be set
|
28
|
-
#
|
29
|
-
# @return [Integer]
|
30
|
-
def size
|
31
|
-
@tempfile.size
|
32
|
-
end
|
33
|
-
|
34
|
-
# Returns the path to the `Tempfile`, so that Rack::Sendfile can send this response
|
35
|
-
# using the downstream webserver
|
36
|
-
#
|
37
|
-
# @return [String]
|
38
|
-
def to_path
|
39
|
-
@tempfile.to_path
|
40
|
-
end
|
41
|
-
|
42
|
-
# Stream the file's contents if `Rack::Sendfile` isn't present.
|
43
|
-
#
|
44
|
-
# @return [void]
|
45
|
-
def each
|
46
|
-
@tempfile.rewind
|
47
|
-
while chunk = @tempfile.read(16384)
|
48
|
-
yield chunk
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|