zip_kit 6.2.1 → 6.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1d33b58f4501d3ddbae7abcab3957fde0549abf734eae72ca1a7ce45601f479
4
- data.tar.gz: e9126924e6fe75329237ba551a1a65218676c7d2b3757f4ad91e73eb0bce154e
3
+ metadata.gz: e1136ebba851638486c9e47150a8d706c49a2bbc0074f457c794582d8ce19089
4
+ data.tar.gz: 80de3edcb5bc748aaf855a7bf0b1f19439522c8efa4b97754f813fe9413bac2c
5
5
  SHA512:
6
- metadata.gz: 011e57f856ebe7f625b0bfa5eeb4a240c6c38f2b07ff0434b7e89805516b2d47b6d4230ac404203d00562586909c72d7ea225a7238d47af70fb99ed97b3d50bc
7
- data.tar.gz: b68fbaae2e57314c47e7971aeef2150341ba80308c7dee1536718250f5cac01b4b387fe3f73cde5f84fb1ffcd93500343ec451d0fccf9f19b2c0c4a58e74aa2f
6
+ metadata.gz: 20c5922a4178f2068a4f06388b201bd263f01c387d308c2c6297feba1c05385d601072cae0451d59a4a0b4e1ba1e354a6fa7f622ff1b58daf70947e6991b1e82
7
+ data.tar.gz: c373972ec6980000b40d1808247759b44f317a7aa3795b406e02005412cf0687e0f2311e5809f011eb1fbc19e6b2b7eb2a6a8f036cafe27a2645f6476cf0c441
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 6.2.2
2
+
3
+ * Make sure "zlib" gets required at the top, as it is used everywhere
4
+ * Improve documentation
5
+ * Make sure `zip_kit_stream` honors the custom `Content-Type` parameter
6
+ * Add a streaming example with Sinatra (and add a Sinatra app to the test harness)
7
+
1
8
  ## 6.2.1
2
9
 
3
10
  * Make `RailsStreaming` compatible with `ActionController::Live` (previously the response would hang)
data/README.md CHANGED
@@ -55,9 +55,9 @@ If you want some more conveniences you can also use [zipline](https://github.com
55
55
  will automatically process and stream attachments (Carrierwave, Shrine, ActiveStorage) and remote objects
56
56
  via HTTP.
57
57
 
58
- `RailsStreaming` will *not* use [ActionController::Live](https://api.rubyonrails.org/classes/ActionController/Live.html)
59
- and the ZIP output will run in the same thread as your main request. Your testing flows (be it minitest or
60
- RSpec) should work normally with controller actions returning ZIPs.
58
+ `RailsStreaming` does *not* require [ActionController::Live](https://api.rubyonrails.org/classes/ActionController/Live.html)
59
+ and will stream without it. See {ZipKit::RailsStreaming#zip_kit_stream} for more details on this. You can use it
60
+ together with `Live` just fine if you need to.
61
61
 
62
62
  ## Writing into streaming destinations
63
63
 
@@ -108,11 +108,17 @@ zip.write_file('line_items.csv') do |sink|
108
108
  end
109
109
  ```
110
110
 
111
- ## Create a ZIP file without size estimation, compress on-the-fly during writes
111
+ ## Automatic storage mode (stored vs. deflated)
112
112
 
113
- Basic use case is compressing on the fly. Some data will be buffered by the Zlib deflater, but
114
- memory inflation is going to be very constrained. Data will be written to destination at fairly regular
115
- intervals. Deflate compression will work best for things like text files. For example, here is how to
113
+ The ZIP file format allows storage in both compressed and raw storage modes. The raw ("stored")
114
+ mode does not require decompression and unarchives faster.
115
+
116
+ ZipKit will buffer a small amount of output and attempt to compress it using deflate compression.
117
+ If this turns out to be significantly smaller than raw data, it is then going to proceed with
118
+ all further output using deflate compression. Memory use is going to be very modest, but it allows
119
+ you to not have to think about the appropriate storage mode.
120
+
121
+ Deflate compression will work great for JSONs, CSVs and other text- or text-like formats. For example, here is how to
116
122
  output direct to STDOUT (so that you can run `$ ruby archive.rb > file.zip` in your terminal):
117
123
 
118
124
  ```ruby
@@ -126,8 +132,8 @@ ZipKit::Streamer.open($stdout) do |zip|
126
132
  end
127
133
  ```
128
134
 
129
- Unfortunately with this approach it is impossible to compute the size of the ZIP file being output,
130
- since you do not know how large the compressed data segments are going to be.
135
+ If you want to use specific storage modes, use `write_deflated_file` and `write_stored_file` instead of
136
+ `write_file`.
131
137
 
132
138
  ## Send a ZIP from a Rack response
133
139
 
@@ -152,7 +158,11 @@ end
152
158
 
153
159
  ## Send a ZIP file of known size, with correct headers
154
160
 
155
- Use the `SizeEstimator` to compute the correct size of the resulting archive.
161
+ Sending a file with data descriptors is not always desirable - you don't really know how large your ZIP is going to be.
162
+ If you want to present your users with proper download progress, you would need to set a `Content-Length` header - and
163
+ know ahead of time how large your download is going to be. This can be done with ZipKit, provided you know how large
164
+ the compressed versions of your file are going to be. Use the {ZipKit::SizeEstimator} to do the pre-calculation - it
165
+ is not going to produce any large amounts of output, and will give you a to-the-byte value for your future archive:
156
166
 
157
167
  ```ruby
158
168
  bytesize = ZipKit::SizeEstimator.estimate do |z|
@@ -0,0 +1,16 @@
1
+ require "sinatra/base"
2
+
3
+ class SinatraApp < Sinatra::Base
4
+ get "/" do
5
+ content_type :zip
6
+ stream do |out|
7
+ ZipKit::Streamer.open(out) do |z|
8
+ z.write_file(File.basename(__FILE__)) do |io|
9
+ File.open(__FILE__, "r") do |f|
10
+ IO.copy_stream(f, io)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "zlib"
4
-
5
3
  # Permits Deflate compression in independent blocks. The workflow is as follows:
6
4
  #
7
5
  # * Run every block to compress through deflate_chunk, remove the header,
@@ -34,6 +34,15 @@ require "time" # for .httpdate
34
34
  #
35
35
  # to bypass things like `Rack::ETag` and the nginx buffering.
36
36
  class ZipKit::OutputEnumerator
37
+ # With HTTP output it is better to apply a small amount of buffering. While Streamer
38
+ # output does not buffer at all, the `OutputEnumerator` does as it is going to
39
+ # be used as a Rack response body. Applying some buffering helps reduce the number
40
+ # of syscalls for otherwise tiny writes, which relieves the app webserver from
41
+ # doing too much work managing those writes. While we recommend buffering, the
42
+ # buffer size is configurable via the constructor - so you can disable buffering
43
+ # if you really need to. While ZipKit ams not to buffer, in this instance this
44
+ # buffering is justified. See https://github.com/WeTransfer/zip_tricks/issues/78
45
+ # for the background on buffering.
37
46
  DEFAULT_WRITE_BUFFER_SIZE = 64 * 1024
38
47
 
39
48
  # Creates a new OutputEnumerator enumerator. The enumerator can be read from using `each`,
@@ -5,14 +5,29 @@ module ZipKit::RailsStreaming
5
5
  # Opens a {ZipKit::Streamer} and yields it to the caller. The output of the streamer
6
6
  # gets automatically forwarded to the Rails response stream. When the output completes,
7
7
  # the Rails response stream is going to be closed automatically.
8
+ #
9
+ # Note that there is an important difference in how this method works, depending whether
10
+ # you use it in a controller which includes `ActionController::Live` vs. one that does not.
11
+ # With a standard `ActionController` this method will assign a response body, but streaming
12
+ # will begin when your action method returns. With `ActionController::Live` the streaming
13
+ # will begin immediately, before the method returns. In all other aspects the method should
14
+ # stream correctly in both types of controllers.
15
+ #
16
+ # If you encounter buffering (streaming does not start for a very long time) you probably
17
+ # have a piece of Rack middleware in your stack which buffers. Known offenders are `Rack::ContentLength`,
18
+ # `Rack::MiniProfiler` and `Rack::ETag`. ZipKit will try to work around these but it is not
19
+ # always possible. If you encounter buffering, examine your middleware stack and try to suss
20
+ # out whether any middleware might be buffering. You can also try setting `use_chunked_transfer_encoding`
21
+ # to `true` - this is not recommended but sometimes necessary, for example to bypass `Rack::ContentLength`.
22
+ #
8
23
  # @param filename[String] name of the file for the Content-Disposition header
9
24
  # @param type[String] the content type (MIME type) of the archive being output
10
25
  # @param use_chunked_transfer_encoding[Boolean] whether to forcibly encode output as chunked. Normally you should not need this.
11
- # @param zip_streamer_options[Hash] options that will be passed to the Streamer.
12
- # See {ZipKit::Streamer#initialize} for the full list of options.
26
+ # @param output_enumerator_options[Hash] options that will be passed to the OutputEnumerator - these include
27
+ # options for the Streamer. See {ZipKit::OutputEnumerator#initialize} for the full list of options.
13
28
  # @yieldparam [ZipKit::Streamer] the streamer that can be written to
14
- # @return [ZipKit::OutputEnumerator] The output enumerator assigned to the response body
15
- def zip_kit_stream(filename: "download.zip", type: "application/zip", use_chunked_transfer_encoding: false, **zip_streamer_options, &zip_streaming_blk)
29
+ # @return [Boolean] always returns true
30
+ def zip_kit_stream(filename: "download.zip", type: "application/zip", use_chunked_transfer_encoding: false, **output_enumerator_options, &zip_streaming_blk)
16
31
  # We want some common headers for file sending. Rails will also set
17
32
  # self.sending_file = true for us when we call send_file_headers!
18
33
  send_file_headers!(type: type, filename: filename)
@@ -26,38 +41,45 @@ module ZipKit::RailsStreaming
26
41
  end
27
42
 
28
43
  headers = ZipKit::OutputEnumerator.streaming_http_headers
29
- response.headers.merge!(headers)
44
+
45
+ # Allow Rails headers to override ours. This is important if, for example, a content type gets
46
+ # set to something else than "application/zip"
47
+ response.headers.reverse_merge!(headers)
30
48
 
31
49
  # The output enumerator yields chunks of bytes generated from the Streamer,
32
- # with some buffering
33
- output_enum = ZipKit::OutputEnumerator.new(**zip_streamer_options, &zip_streaming_blk)
50
+ # with some buffering. See OutputEnumerator docs for more.
51
+ rack_zip_body = ZipKit::OutputEnumerator.new(**output_enumerator_options, &zip_streaming_blk)
52
+
53
+ # Chunked encoding may be forced if, for example, you _need_ to bypass Rack::ContentLength.
54
+ # Rack::ContentLength is normally not in a Rails middleware stack, but it might get
55
+ # introduced unintentionally - for example, "rackup" adds the ContentLength middleware for you.
56
+ # There is a recommendation to leave the chunked encoding to the app server, so that servers
57
+ # that support HTTP/2 can use native framing and not have to deal with the chunked encoding,
58
+ # see https://github.com/julik/zip_kit/issues/7
59
+ # But it is not to be excluded that a user may need to force the chunked encoding to bypass
60
+ # some especially pesky Rack middleware that just would not cooperate. Those include
61
+ # Rack::MiniProfiler and the above-mentioned Rack::ContentLength.
62
+ if use_chunked_transfer_encoding
63
+ response.headers["Transfer-Encoding"] = "chunked"
64
+ rack_zip_body = ZipKit::RackChunkedBody.new(rack_zip_body)
65
+ end
34
66
 
35
67
  # Time for some branching, which mostly has to do with the 999 flavours of
36
68
  # "how to make both Rails and Rack stream"
37
69
  if self.class.ancestors.include?(ActionController::Live)
38
70
  # If this controller includes Live it will not work correctly with a Rack
39
- # response body assignment - we need to write into the Live output stream instead
71
+ # response body assignment - the action will just hang. We need to read out the response
72
+ # body ourselves and write it into the Rails stream.
40
73
  begin
41
- output_enum.each { |bytes| response.stream.write(bytes) }
74
+ rack_zip_body.each { |bytes| response.stream.write(bytes) }
42
75
  ensure
43
76
  response.stream.close
44
77
  end
45
- elsif use_chunked_transfer_encoding
46
- # Chunked encoding may be forced if, for example, you _need_ to bypass Rack::ContentLength.
47
- # Rack::ContentLength is normally not in a Rails middleware stack, but it might get
48
- # introduced unintentionally - for example, "rackup" adds the ContentLength middleware for you.
49
- # There is a recommendation to leave the chunked encoding to the app server, so that servers
50
- # that support HTTP/2 can use native framing and not have to deal with the chunked encoding,
51
- # see https://github.com/julik/zip_kit/issues/7
52
- # But it is not to be excluded that a user may need to force the chunked encoding to bypass
53
- # some especially pesky Rack middleware that just would not cooperate. Those include
54
- # Rack::MiniProfiler and the above-mentioned Rack::ContentLength.
55
- response.headers["Transfer-Encoding"] = "chunked"
56
- self.response_body = ZipKit::RackChunkedBody.new(output_enum)
57
78
  else
58
- # Stream using a Rack body assigned to the ActionController response body, without
59
- # doing explicit chunked encoding. See above for the reasoning.
60
- self.response_body = output_enum
79
+ # Stream using a Rack body assigned to the ActionController response body
80
+ self.response_body = rack_zip_body
61
81
  end
82
+
83
+ true
62
84
  end
63
85
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZipKit
4
- VERSION = "6.2.1"
4
+ VERSION = "6.2.2"
5
5
  end
data/lib/zip_kit.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "zip_kit/version"
4
+ require "zlib"
4
5
 
5
6
  module ZipKit
6
7
  autoload :OutputEnumerator, File.dirname(__FILE__) + "/zip_kit/rack_body.rb"
data/rbi/zip_kit.rbi CHANGED
@@ -1,6 +1,6 @@
1
1
  # typed: strong
2
2
  module ZipKit
3
- VERSION = T.let("6.2.1", T.untyped)
3
+ VERSION = T.let("6.2.2", T.untyped)
4
4
 
5
5
  # A ZIP archive contains a flat list of entries. These entries can implicitly
6
6
  # create directories when the archive is expanded. For example, an entry with
@@ -1979,25 +1979,39 @@ end, T.untyped)
1979
1979
  # gets automatically forwarded to the Rails response stream. When the output completes,
1980
1980
  # the Rails response stream is going to be closed automatically.
1981
1981
  #
1982
+ # Note that there is an important difference in how this method works, depending whether
1983
+ # you use it in a controller which includes `ActionController::Live` vs. one that does not.
1984
+ # With a standard `ActionController` this method will assign a response body, but streaming
1985
+ # will begin when your action method returns. With `ActionController::Live` the streaming
1986
+ # will begin immediately, before the method returns. In all other aspects the method should
1987
+ # stream correctly in both types of controllers.
1988
+ #
1989
+ # If you encounter buffering (streaming does not start for a very long time) you probably
1990
+ # have a piece of Rack middleware in your stack which buffers. Known offenders are `Rack::ContentLength`,
1991
+ # `Rack::MiniProfiler` and `Rack::ETag`. ZipKit will try to work around these but it is not
1992
+ # always possible. If you encounter buffering, examine your middleware stack and try to suss
1993
+ # out whether any middleware might be buffering. You can also try setting `use_chunked_transfer_encoding`
1994
+ # to `true` - this is not recommended but sometimes necessary, for example to bypass `Rack::ContentLength`.
1995
+ #
1982
1996
  # _@param_ `filename` — name of the file for the Content-Disposition header
1983
1997
  #
1984
1998
  # _@param_ `type` — the content type (MIME type) of the archive being output
1985
1999
  #
1986
2000
  # _@param_ `use_chunked_transfer_encoding` — whether to forcibly encode output as chunked. Normally you should not need this.
1987
2001
  #
1988
- # _@param_ `zip_streamer_options` — options that will be passed to the Streamer. See {ZipKit::Streamer#initialize} for the full list of options.
2002
+ # _@param_ `output_enumerator_options` — options that will be passed to the OutputEnumerator - these include options for the Streamer. See {ZipKit::OutputEnumerator#initialize} for the full list of options.
1989
2003
  #
1990
- # _@return_ — The output enumerator assigned to the response body
2004
+ # _@return_ — always returns true
1991
2005
  sig do
1992
2006
  params(
1993
2007
  filename: String,
1994
2008
  type: String,
1995
2009
  use_chunked_transfer_encoding: T::Boolean,
1996
- zip_streamer_options: T::Hash[T.untyped, T.untyped],
2010
+ output_enumerator_options: T::Hash[T.untyped, T.untyped],
1997
2011
  zip_streaming_blk: T.proc.params(the: ZipKit::Streamer).void
1998
- ).returns(ZipKit::OutputEnumerator)
2012
+ ).returns(T::Boolean)
1999
2013
  end
2000
- def zip_kit_stream(filename: "download.zip", type: "application/zip", use_chunked_transfer_encoding: false, **zip_streamer_options, &zip_streaming_blk); end
2014
+ def zip_kit_stream(filename: "download.zip", type: "application/zip", use_chunked_transfer_encoding: false, **output_enumerator_options, &zip_streaming_blk); end
2001
2015
  end
2002
2016
 
2003
2017
  # The output enumerator makes it possible to "pull" from a ZipKit streamer
data/zip_kit.gemspec CHANGED
@@ -42,5 +42,6 @@ Gem::Specification.new do |spec|
42
42
  spec.add_development_dependency "puma"
43
43
  spec.add_development_dependency "actionpack", "~> 5" # For testing RailsStreaming against an actual Rails controller
44
44
  spec.add_development_dependency "nokogiri", "~> 1", ">= 1.13" # Rails 5 does by mistake use an older Nokogiri otherwise
45
+ spec.add_development_dependency "sinatra"
45
46
  spec.add_development_dependency "sord"
46
47
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zip_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.2.1
4
+ version: 6.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: exe
14
14
  cert_chain: []
15
- date: 2024-03-23 00:00:00.000000000 Z
15
+ date: 2024-03-27 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: bundler
@@ -250,6 +250,20 @@ dependencies:
250
250
  - - ">="
251
251
  - !ruby/object:Gem::Version
252
252
  version: '1.13'
253
+ - !ruby/object:Gem::Dependency
254
+ name: sinatra
255
+ requirement: !ruby/object:Gem::Requirement
256
+ requirements:
257
+ - - ">="
258
+ - !ruby/object:Gem::Version
259
+ version: '0'
260
+ type: :development
261
+ prerelease: false
262
+ version_requirements: !ruby/object:Gem::Requirement
263
+ requirements:
264
+ - - ">="
265
+ - !ruby/object:Gem::Version
266
+ version: '0'
253
267
  - !ruby/object:Gem::Dependency
254
268
  name: sord
255
269
  requirement: !ruby/object:Gem::Requirement
@@ -292,6 +306,7 @@ files:
292
306
  - examples/parallel_compression_with_block_deflate.rb
293
307
  - examples/rack_application.rb
294
308
  - examples/s3_upload.rb
309
+ - examples/sinatra_application.rb
295
310
  - lib/zip_kit.rb
296
311
  - lib/zip_kit/block_deflate.rb
297
312
  - lib/zip_kit/block_write.rb
@@ -343,7 +358,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
343
358
  - !ruby/object:Gem::Version
344
359
  version: '0'
345
360
  requirements: []
346
- rubygems_version: 3.3.7
361
+ rubygems_version: 3.1.6
347
362
  signing_key:
348
363
  specification_version: 4
349
364
  summary: Stream out ZIP files from Ruby. Successor to zip_tricks.