zip_kit 6.2.1 → 6.2.2

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 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.