rack-compress 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8f39deacc15e2eab7f57931ee5b7995ebb86307d8acab163fc8a98fa0b8e9648
4
+ data.tar.gz: 32e598f17eb9257a5294ea828faaa0c904a7da25c9afb70b662e77ecef0048e5
5
+ SHA512:
6
+ metadata.gz: 158674e688d525c79dd061c51b956233620955cf474a59b32865c6ef6205d21001f5dac346e3d16472d087b9f98bb217d8e83642469f6a6999efda3586ead73a
7
+ data.tar.gz: 99613bc5f42a865b981dae1713e606623cbddf7c059c6e65b263010b7a4fda21e1bc5bd5bb4763bcdca13ef27d7f6991bd15d458f01f94703402b45e5401240f
data/COPYING ADDED
@@ -0,0 +1,19 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2008 The Committers
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to
6
+ deal in the Software without restriction, including without limitation the
7
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8
+ sell copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ [![Gem Version](https://badge.fury.io/rb/rack-compress.svg)](https://badge.fury.io/rb/rack-compress) [![Build Status](https://github.com/andrepiske/rack-compress/actions/workflows/test.yml/badge.svg)](https://github.com/andrepiske/rack-compress/actions/workflows/test.yml)
2
+
3
+ # Rack::Compress
4
+
5
+
6
+ `Rack::Compress` compresses `Rack` responses using [Google's Brotli](https://github.com/google/brotli) and [Facebook's Zstandard](https://github.com/facebook/zstd) compression algorithms.
7
+
8
+ Those generally compresses better than `gzip` for the same CPU cost.
9
+ Brotli is supported by [Chrome, Firefox, IE and Opera](http://caniuse.com/#feat=brotli), while
10
+ Zstandard (aka. Zstd) began making its way into major browsers and as of today it's available on Chrome behind feature flags, see [the caniuse page](https://caniuse.com/zstd).
11
+
12
+ ### Use
13
+
14
+ Install gem:
15
+
16
+ gem install rack-compress
17
+
18
+ Requiring `'rack/compress'` will autoload `Rack::compress` module.
19
+ The following example shows what a simple rackup
20
+ (`config.ru`) file might look like:
21
+
22
+ ```ruby
23
+ require 'rack'
24
+ require 'rack/compress'
25
+
26
+ use Rack::Compress
27
+
28
+ run theapp
29
+ ```
30
+
31
+ Note that it is up to the browser or the HTTP client to choose the compression algorithm.
32
+ This occurs via the [accept-encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding)
33
+ header. `Rack::Compress` always gives priority to `zstd` when the client supports it, since
34
+ it should perform better than Brotli
35
+ [according to benchmarking](https://github.com/andrepiske/compress-benchmark#conclusions).
36
+
37
+ It's possible to also customize the compression levels for each algorithm:
38
+
39
+ ```ruby
40
+ use Rack::Compress, {
41
+ levels: {
42
+ brotli: 11, # must be between 0 and 11
43
+ zstd: 19 # must be between 1 and 19
44
+ }
45
+ }
46
+ ```
47
+
48
+ In case you want to better control which MIME types get compressed:
49
+
50
+ ```ruby
51
+ use Rack::Compress, { include: [
52
+ 'text/html',
53
+ 'text/css',
54
+ 'application/javascript',
55
+ ] }
56
+ ```
57
+
58
+ The above will compress all those MIME types and not any other.
59
+
60
+ ### Testing
61
+
62
+ To run the entire test suite, run
63
+
64
+ rake test
65
+
66
+ ### Acknowledgements
67
+
68
+ Thanks to [Marco Costa](https://github.com/marcotc) for the original gem form which this one
69
+ was forked from.
70
+
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/utils'
4
+ require 'zstd-ruby'
5
+ require 'brotli'
6
+
7
+ module Rack::Compress
8
+ # This middleware enables compression of http responses.
9
+ #
10
+ # Currently supported compression algorithms:
11
+ #
12
+ # * br # gem 'brotli'
13
+ # * zstd # gem 'extlz4'
14
+ #
15
+ # The middleware automatically detects when compression is supported
16
+ # and allowed. For example no transformation is made when a cache
17
+ # directive of 'no-transform' is present, or when the response status
18
+ # code is one that doesn't allow an entity body.
19
+ class Deflater
20
+ ##
21
+ # Creates Rack::Compress middleware.
22
+ #
23
+ # [app] rack app instance
24
+ # [options] hash of deflater options, i.e.
25
+ # 'if' - a lambda enabling / disabling deflation based on returned boolean value
26
+ # e.g use Rack::Brotli, :if => lambda { |env, status, headers, body| body.map(&:bytesize).reduce(0, :+) > 512 }
27
+ # 'include' - a list of content types that should be compressed
28
+ # 'levels' - Compression levels
29
+ def initialize(app, options = {})
30
+ @app = app
31
+
32
+ @condition = options[:if]
33
+ @compressible_types = options[:include]
34
+ @levels_options = { brotli: 4, zstd: 5 }.merge(options[:levels] || {})
35
+ end
36
+
37
+ def call(env)
38
+ status, headers, body = @app.call(env)
39
+ headers = Rack::Utils::HeaderHash.new(headers)
40
+
41
+ unless should_deflate?(env, status, headers, body)
42
+ return [status, headers, body]
43
+ end
44
+
45
+ request = Rack::Request.new(env)
46
+
47
+ encoding = Rack::Utils.select_best_encoding(%w(zstd br), request.accept_encoding)
48
+
49
+ return [status, headers, body] unless encoding
50
+
51
+ # Set the Vary HTTP header.
52
+ vary = headers["vary"].to_s.split(",").map(&:strip)
53
+ unless vary.include?("*") || vary.include?("accept-encoding")
54
+ headers["vary"] = vary.push("accept-encoding").join(",")
55
+ end
56
+
57
+ case encoding
58
+ when "zstd"
59
+ headers['content-encoding'] = "zstd"
60
+ headers.delete(Rack::CONTENT_LENGTH)
61
+ [status, headers, ZstandardStream.new(body, @levels_options[:zstd])]
62
+ when "br"
63
+ headers['content-encoding'] = "br"
64
+ headers.delete(Rack::CONTENT_LENGTH)
65
+ [status, headers, BrotliStream.new(body, @levels_options[:brotli])]
66
+ when nil
67
+ message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
68
+ bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
69
+ [406, {Rack::CONTENT_TYPE => "text/plain", Rack::CONTENT_LENGTH => message.length.to_s}, bp]
70
+ end
71
+ end
72
+
73
+ class BrotliStream
74
+ include Rack::Utils
75
+
76
+ def initialize(body, level)
77
+ @body = body
78
+ @level = level
79
+ end
80
+
81
+ def each(&block)
82
+ @writer = block
83
+ # Use String.new instead of '' to support environments with strings frozen by default.
84
+ buffer = String.new
85
+ @body.each do |part|
86
+ buffer << part
87
+ end
88
+
89
+ # TODO: implement Brotli using streaming classes
90
+
91
+ yield ::Brotli.deflate(buffer, { quality: @level })
92
+ ensure
93
+ @writer = nil
94
+ end
95
+
96
+ def close
97
+ @body.close if @body.respond_to?(:close)
98
+ end
99
+ end
100
+
101
+ class ZstandardStream
102
+ include Rack::Utils
103
+
104
+ def initialize(body, level)
105
+ @body = body
106
+ @level = level
107
+ @compressor = ::Zstd::StreamingCompress.new(level)
108
+ end
109
+
110
+ def each(&block)
111
+ @writer = block
112
+
113
+ @body.each do |part|
114
+ @compressor << part
115
+ end
116
+
117
+ yield @compressor.finish
118
+ ensure
119
+ @writer = nil
120
+ end
121
+
122
+ def close
123
+ @body.close if @body.respond_to?(:close)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def should_deflate?(env, status, headers, body)
130
+ # Skip compressing empty entity body responses and responses with
131
+ # no-transform set.
132
+ if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
133
+ headers[Rack::CACHE_CONTROL].to_s =~ /\bno-transform\b/ ||
134
+ (headers['content-encoding'] && headers['content-encoding'] !~ /\bidentity\b/)
135
+ return false
136
+ end
137
+
138
+ # Skip if @compressible_types are given and does not include request's content type
139
+ return false if @compressible_types && !(headers.has_key?(Rack::CONTENT_TYPE) && @compressible_types.include?(headers[Rack::CONTENT_TYPE][/[^;]*/]))
140
+
141
+ # Skip if @condition lambda is given and evaluates to false
142
+ return false if @condition && !@condition.call(env, status, headers, body)
143
+
144
+ true
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ module Compress
5
+ module Version
6
+ def self.to_s
7
+ '0.1.0'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'compress/deflater'
2
+ require_relative 'compress/version'
3
+
4
+ module Rack
5
+ module Compress
6
+ def self.release
7
+ Version.to_s
8
+ end
9
+
10
+ def self.new(app, options={})
11
+ Rack::Compress::Deflater.new(app, options)
12
+ end
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-compress
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - André Diego Piske
8
+ - Marco Costa
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2023-10-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '1.4'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '1.4'
28
+ - !ruby/object:Gem::Dependency
29
+ name: brotli
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 0.1.7
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 0.1.7
42
+ - !ruby/object:Gem::Dependency
43
+ name: zstd-ruby
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '1.5'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '1.5'
56
+ - !ruby/object:Gem::Dependency
57
+ name: bundler
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: minitest
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '5.6'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '5.6'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rake
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '12'
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: 12.3.3
94
+ type: :development
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '12'
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 12.3.3
104
+ - !ruby/object:Gem::Dependency
105
+ name: rdoc
106
+ requirement: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.12'
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.12'
118
+ description: Rack::Compress enables Zstd and Brotli compression on HTTP responses
119
+ email: andrepiske@gmail.com
120
+ executables: []
121
+ extensions: []
122
+ extra_rdoc_files:
123
+ - README.md
124
+ - COPYING
125
+ files:
126
+ - COPYING
127
+ - README.md
128
+ - lib/rack/compress.rb
129
+ - lib/rack/compress/deflater.rb
130
+ - lib/rack/compress/version.rb
131
+ homepage: http://github.com/andrepiske/rack-compress/
132
+ licenses:
133
+ - MIT
134
+ metadata: {}
135
+ post_install_message:
136
+ rdoc_options:
137
+ - "--line-numbers"
138
+ - "--inline-source"
139
+ - "--title"
140
+ - rack-brotli
141
+ - "--main"
142
+ - README
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubygems_version: 3.4.20
157
+ signing_key:
158
+ specification_version: 2
159
+ summary: Compression for Rack responses
160
+ test_files: []