rack-compress 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +19 -0
- data/README.md +70 -0
- data/lib/rack/compress/deflater.rb +147 -0
- data/lib/rack/compress/version.rb +11 -0
- data/lib/rack/compress.rb +14 -0
- metadata +160 -0
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
|
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: []
|