rack-contrib 2.1.0 → 2.2.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.
Potentially problematic release.
This version of rack-contrib might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/lib/rack/contrib.rb +1 -0
- data/lib/rack/contrib/csshttprequest.rb +1 -1
- data/lib/rack/contrib/json_body_parser.rb +83 -0
- data/lib/rack/contrib/jsonp.rb +1 -1
- data/lib/rack/contrib/locale.rb +62 -30
- data/lib/rack/contrib/post_body_content_type_parser.rb +44 -2
- data/lib/rack/contrib/response_cache.rb +14 -4
- data/lib/rack/contrib/static_cache.rb +5 -3
- metadata +26 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca58db2b7968ad4691e8bec79eea2088dabc3da5d309358f7df76d668324d824
|
4
|
+
data.tar.gz: 51816bb6b59a598fea16eb100cc783b3c0ff58eb37aa3585bb8d65141f2aae4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7442b5ad170ae2946f017d8ccd007d02455b7939fe42239378505b61983cd9a1f090549e87b07e08323a7cf99d04f026e8df0f326e34be166508187cca3735e7
|
7
|
+
data.tar.gz: 99985100672c65c9ee559a37281f5dddea4fcdc8481ddffd75c2f6e697197a1070339c5b70733d736f8ba33078d5f699f00097cd6ec96266c8f07edb008b727b
|
data/README.md
CHANGED
@@ -13,6 +13,7 @@ interface:
|
|
13
13
|
* `Rack::Deflect` - Helps protect against DoS attacks.
|
14
14
|
* `Rack::Evil` - Lets the rack application return a response to the client from any place.
|
15
15
|
* `Rack::HostMeta` - Configures `/host-meta` using a block
|
16
|
+
* `Rack::JSONBodyParser` - Adds JSON request bodies to the Rack parameters hash.
|
16
17
|
* `Rack::JSONP` - Adds JSON-P support by stripping out the callback param and padding the response with the appropriate callback format.
|
17
18
|
* `Rack::LazyConditionalGet` - Caches a global `Last-Modified` date and updates it each time there is a request that is not `GET` or `HEAD`.
|
18
19
|
* `Rack::LighttpdScriptNameFix` - Fixes how lighttpd sets the `SCRIPT_NAME` and `PATH_INFO` variables in certain configurations.
|
@@ -20,7 +21,7 @@ interface:
|
|
20
21
|
* `Rack::MailExceptions` - Rescues exceptions raised from the app and sends a useful email with the exception, stacktrace, and contents of the environment.
|
21
22
|
* `Rack::NestedParams` - parses form params with subscripts (e.g., * "`post[title]=Hello`") into a nested/recursive Hash structure (based on Rails' implementation).
|
22
23
|
* `Rack::NotFound` - A default 404 application.
|
23
|
-
* `Rack::PostBodyContentTypeParser` - Adds support for JSON request bodies. The Rack parameter hash is populated by deserializing the JSON data provided in the request body when the Content-Type is application/json
|
24
|
+
* `Rack::PostBodyContentTypeParser` - [Deprecated]: Adds support for JSON request bodies. The Rack parameter hash is populated by deserializing the JSON data provided in the request body when the Content-Type is application/json
|
24
25
|
* `Rack::Printout` - Prints the environment and the response per request
|
25
26
|
* `Rack::ProcTitle` - Displays request information in process title (`$0`) for monitoring/inspection with ps(1).
|
26
27
|
* `Rack::Profiler` - Uses ruby-prof to measure request time.
|
data/lib/rack/contrib.rb
CHANGED
@@ -24,6 +24,7 @@ module Rack
|
|
24
24
|
autoload :HostMeta, "rack/contrib/host_meta"
|
25
25
|
autoload :GarbageCollector, "rack/contrib/garbagecollector"
|
26
26
|
autoload :JSONP, "rack/contrib/jsonp"
|
27
|
+
autoload :JSONBodyParser, "rack/contrib/json_body_parser"
|
27
28
|
autoload :LazyConditionalGet, "rack/contrib/lazy_conditional_get"
|
28
29
|
autoload :LighttpdScriptNameFix, "rack/contrib/lighttpd_script_name_fix"
|
29
30
|
autoload :Locale, "rack/contrib/locale"
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
# A Rack middleware that makes JSON-encoded request bodies available in the
|
7
|
+
# request.params hash. By default it parses POST, PATCH, and PUT requests
|
8
|
+
# whose media type is <tt>application/json</tt>. You can configure it to match
|
9
|
+
# any verb or media type via the <tt>:verbs</tt> and <tt>:media</tt> options.
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# == Examples:
|
13
|
+
#
|
14
|
+
# === Parse POST and GET requests only
|
15
|
+
# use Rack::JSONBodyParser, verbs: ['POST', 'GET']
|
16
|
+
#
|
17
|
+
# === Parse POST|PATCH|PUT requests whose Content-Type matches 'json'
|
18
|
+
# use Rack::JSONBodyParser, media: /json/
|
19
|
+
#
|
20
|
+
# === Parse POST requests whose Content-Type is 'application/json' or 'application/vnd+json'
|
21
|
+
# use Rack::JSONBodyParser, verbs: ['POST'], media: ['application/json', 'application/vnd.api+json']
|
22
|
+
#
|
23
|
+
class JSONBodyParser
|
24
|
+
CONTENT_TYPE_MATCHERS = {
|
25
|
+
String => lambda { |option, header|
|
26
|
+
Rack::MediaType.type(header) == option
|
27
|
+
},
|
28
|
+
Array => lambda { |options, header|
|
29
|
+
media_type = Rack::MediaType.type(header)
|
30
|
+
options.any? { |opt| media_type == opt }
|
31
|
+
},
|
32
|
+
Regexp => lambda {
|
33
|
+
if //.respond_to?(:match?)
|
34
|
+
# Use Ruby's fast regex matcher when available
|
35
|
+
->(option, header) { option.match? header }
|
36
|
+
else
|
37
|
+
# Fall back to the slower matcher for rubies older than 2.4
|
38
|
+
->(option, header) { option.match header }
|
39
|
+
end
|
40
|
+
}.call(),
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
DEFAULT_PARSER = ->(body) { JSON.parse(body, create_additions: false) }
|
44
|
+
|
45
|
+
def initialize(
|
46
|
+
app,
|
47
|
+
verbs: %w[POST PATCH PUT],
|
48
|
+
media: 'application/json',
|
49
|
+
&block
|
50
|
+
)
|
51
|
+
@app = app
|
52
|
+
@verbs, @media = verbs, media
|
53
|
+
@matcher = CONTENT_TYPE_MATCHERS.fetch(@media.class)
|
54
|
+
@parser = block || DEFAULT_PARSER
|
55
|
+
end
|
56
|
+
|
57
|
+
def call(env)
|
58
|
+
if @verbs.include?(env[Rack::REQUEST_METHOD]) &&
|
59
|
+
@matcher.call(@media, env['CONTENT_TYPE'])
|
60
|
+
|
61
|
+
update_form_hash_with_json_body(env)
|
62
|
+
end
|
63
|
+
@app.call(env)
|
64
|
+
rescue JSON::ParserError
|
65
|
+
body = { error: 'Failed to parse body as JSON' }.to_json
|
66
|
+
header = { 'Content-Type' => 'application/json' }
|
67
|
+
Rack::Response.new(body, 400, header).finish
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def update_form_hash_with_json_body(env)
|
73
|
+
body = env[Rack::RACK_INPUT]
|
74
|
+
return unless (body_content = body.read) && !body_content.empty?
|
75
|
+
|
76
|
+
body.rewind # somebody might try to read this stream
|
77
|
+
env.update(
|
78
|
+
Rack::RACK_REQUEST_FORM_HASH => @parser.call(body_content),
|
79
|
+
Rack::RACK_REQUEST_FORM_INPUT => body
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/rack/contrib/jsonp.rb
CHANGED
@@ -106,7 +106,7 @@ module Rack
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def bad_request(body = "Bad Request")
|
109
|
-
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.
|
109
|
+
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ]
|
110
110
|
end
|
111
111
|
|
112
112
|
end
|
data/lib/rack/contrib/locale.rb
CHANGED
@@ -7,43 +7,75 @@ module Rack
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def call(env)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
locale_to_restore = I18n.locale
|
11
|
+
|
12
|
+
locale = user_preferred_locale(env["HTTP_ACCEPT_LANGUAGE"])
|
13
|
+
locale ||= I18n.default_locale
|
14
|
+
|
15
|
+
env['rack.locale'] = I18n.locale = locale.to_s
|
16
|
+
status, headers, body = @app.call(env)
|
17
|
+
|
18
|
+
unless headers['Content-Language']
|
19
|
+
headers['Content-Language'] = locale.to_s
|
20
20
|
end
|
21
|
+
|
22
|
+
[status, headers, body]
|
23
|
+
ensure
|
24
|
+
I18n.locale = locale_to_restore
|
21
25
|
end
|
22
26
|
|
23
27
|
private
|
24
28
|
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
29
|
+
# Accept-Language header is covered mainly by RFC 7231
|
30
|
+
# https://tools.ietf.org/html/rfc7231
|
31
|
+
#
|
32
|
+
# Related sections:
|
33
|
+
#
|
34
|
+
# * https://tools.ietf.org/html/rfc7231#section-5.3.1
|
35
|
+
# * https://tools.ietf.org/html/rfc7231#section-5.3.5
|
36
|
+
# * https://tools.ietf.org/html/rfc4647#section-3.4
|
37
|
+
#
|
38
|
+
# There is an obsolete RFC 2616 (https://tools.ietf.org/html/rfc2616)
|
39
|
+
#
|
40
|
+
# Edge cases:
|
41
|
+
#
|
42
|
+
# * Value can be a comma separated list with optional whitespaces:
|
43
|
+
# Accept-Language: da, en-gb;q=0.8, en;q=0.7
|
44
|
+
#
|
45
|
+
# * Quality value can contain optional whitespaces as well:
|
46
|
+
# Accept-Language: ru-UA, ru; q=0.8, uk; q=0.6, en-US; q=0.4, en; q=0.2
|
47
|
+
#
|
48
|
+
# * Quality prefix 'q=' can be in upper case (Q=)
|
49
|
+
#
|
50
|
+
# * Ignore case when match locale with I18n available locales
|
51
|
+
#
|
52
|
+
def user_preferred_locale(header)
|
53
|
+
return if header.nil?
|
54
|
+
|
55
|
+
locales = header.gsub(/\s+/, '').split(",").map do |language_tag|
|
56
|
+
locale, quality = language_tag.split(/;q=/i)
|
57
|
+
quality = quality ? quality.to_f : 1.0
|
58
|
+
[locale, quality]
|
59
|
+
end.reject do |(locale, quality)|
|
60
|
+
locale == '*' || quality == 0
|
61
|
+
end.sort_by do |(_, quality)|
|
62
|
+
quality
|
63
|
+
end.map(&:first)
|
64
|
+
|
65
|
+
return if locales.empty?
|
66
|
+
|
67
|
+
if I18n.enforce_available_locales
|
68
|
+
locale = locales.reverse.find { |locale| I18n.available_locales.any? { |al| match?(al, locale) } }
|
69
|
+
if locale
|
70
|
+
I18n.available_locales.find { |al| match?(al, locale) }
|
42
71
|
end
|
43
|
-
|
72
|
+
else
|
73
|
+
locales.last
|
74
|
+
end
|
75
|
+
end
|
44
76
|
|
45
|
-
|
46
|
-
|
77
|
+
def match?(s1, s2)
|
78
|
+
s1.to_s.casecmp(s2.to_s) == 0
|
47
79
|
end
|
48
80
|
end
|
49
81
|
end
|
@@ -6,10 +6,51 @@ end
|
|
6
6
|
|
7
7
|
module Rack
|
8
8
|
|
9
|
+
# <b>DEPRECATED:</b> <tt>JSONBodyParser</tt> is a drop-in replacement that is faster and more configurable.
|
10
|
+
#
|
9
11
|
# A Rack middleware for parsing POST/PUT body data when Content-Type is
|
10
12
|
# not one of the standard supported types, like <tt>application/json</tt>.
|
11
13
|
#
|
12
|
-
#
|
14
|
+
# === How to use the middleware
|
15
|
+
#
|
16
|
+
# Example of simple +config.ru+ file:
|
17
|
+
#
|
18
|
+
# require 'rack'
|
19
|
+
# require 'rack/contrib'
|
20
|
+
#
|
21
|
+
# use ::Rack::PostBodyContentTypeParser
|
22
|
+
#
|
23
|
+
# app = lambda do |env|
|
24
|
+
# request = Rack::Request.new(env)
|
25
|
+
# body = "Hello #{request.params['name']}"
|
26
|
+
# [200, {'Content-Type' => 'text/plain'}, [body]]
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# run app
|
30
|
+
#
|
31
|
+
# Example with passing block argument:
|
32
|
+
#
|
33
|
+
# use ::Rack::PostBodyContentTypeParser do |body|
|
34
|
+
# { 'params' => JSON.parse(body) }
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# Example with passing proc argument:
|
38
|
+
#
|
39
|
+
# parser = ->(body) { { 'params' => JSON.parse(body) } }
|
40
|
+
# use ::Rack::PostBodyContentTypeParser, &parser
|
41
|
+
#
|
42
|
+
#
|
43
|
+
# === Failed JSON parsing
|
44
|
+
#
|
45
|
+
# Returns "400 Bad request" response if invalid JSON document was sent:
|
46
|
+
#
|
47
|
+
# Raw HTTP response:
|
48
|
+
#
|
49
|
+
# HTTP/1.1 400 Bad Request
|
50
|
+
# Content-Type: text/plain
|
51
|
+
# Content-Length: 28
|
52
|
+
#
|
53
|
+
# failed to parse body as JSON
|
13
54
|
#
|
14
55
|
class PostBodyContentTypeParser
|
15
56
|
|
@@ -25,6 +66,7 @@ module Rack
|
|
25
66
|
APPLICATION_JSON = 'application/json'.freeze
|
26
67
|
|
27
68
|
def initialize(app, &block)
|
69
|
+
warn "[DEPRECATION] `PostBodyContentTypeParser` is deprecated. Use `JSONBodyParser` as a drop-in replacement."
|
28
70
|
@app = app
|
29
71
|
@block = block || Proc.new { |body| JSON.parse(body, :create_additions => false) }
|
30
72
|
end
|
@@ -40,7 +82,7 @@ module Rack
|
|
40
82
|
end
|
41
83
|
|
42
84
|
def bad_request(body = 'Bad Request')
|
43
|
-
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.
|
85
|
+
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ]
|
44
86
|
end
|
45
87
|
end
|
46
88
|
end
|
@@ -3,9 +3,12 @@ require 'rack'
|
|
3
3
|
|
4
4
|
# Rack::ResponseCache is a Rack middleware that caches responses for successful
|
5
5
|
# GET requests with no query string to disk or any ruby object that has an
|
6
|
-
# []= method (so it works with memcached).
|
7
|
-
#
|
8
|
-
# be served
|
6
|
+
# []= method (so it works with memcached). As with Rails' page caching, this
|
7
|
+
# middleware only writes to the cache -- it never reads. The logic of whether a
|
8
|
+
# cached response should be served is left either to your web server, via
|
9
|
+
# something like the <tt>try_files</tt> directive in nginx, or to your
|
10
|
+
# cache-reading middleware of choice, mounted before Rack::ResponseCache in the
|
11
|
+
# stack.
|
9
12
|
class Rack::ResponseCache
|
10
13
|
# The default proc used if a block is not provided to .new
|
11
14
|
# It unescapes the PATH_INFO of the environment, and makes sure that it doesn't
|
@@ -15,7 +18,14 @@ class Rack::ResponseCache
|
|
15
18
|
# of the path to index.html.
|
16
19
|
DEFAULT_PATH_PROC = proc do |env, res|
|
17
20
|
path = Rack::Utils.unescape(env['PATH_INFO'])
|
18
|
-
|
21
|
+
headers = res[1]
|
22
|
+
# Content-Type is almost always at headers['Content-Type'], but to fully
|
23
|
+
# comply with HTTP RFC 7230, we fall back to a case-insensitive lookup
|
24
|
+
content_type = headers.fetch('Content-Type') do |titlecase_key|
|
25
|
+
_, val = headers.find { |key, _| key.casecmp(titlecase_key) == 0 }
|
26
|
+
val
|
27
|
+
end
|
28
|
+
if !path.include?('..') and match = /text\/((?:x|ht)ml|css)/o.match(content_type)
|
19
29
|
type = match[1]
|
20
30
|
path = "#{path}.#{type}" unless /\.#{type}\z/.match(path)
|
21
31
|
path = File.join(File.dirname(path), 'index.html') if type == 'html' and File.basename(path) == '.html'
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
|
3
5
|
#
|
@@ -68,7 +70,6 @@ module Rack
|
|
68
70
|
@version_regex = options.fetch(:version_regex, /-[\d.]+([.][a-zA-Z][\w]+)?$/)
|
69
71
|
end
|
70
72
|
@duration_in_seconds = self.duration_in_seconds
|
71
|
-
@duration_in_words = self.duration_in_words
|
72
73
|
end
|
73
74
|
|
74
75
|
def call(env)
|
@@ -83,14 +84,15 @@ module Rack
|
|
83
84
|
status, headers, body = @file_server.call(env)
|
84
85
|
if @no_cache[url].nil?
|
85
86
|
headers['Cache-Control'] ="max-age=#{@duration_in_seconds}, public"
|
86
|
-
headers['Expires'] =
|
87
|
+
headers['Expires'] = duration_in_words
|
87
88
|
end
|
89
|
+
headers['Date'] = Time.now.httpdate
|
88
90
|
[status, headers, body]
|
89
91
|
end
|
90
92
|
end
|
91
93
|
|
92
94
|
def duration_in_words
|
93
|
-
(Time.now + self.duration_in_seconds).
|
95
|
+
(Time.now.utc + self.duration_in_seconds).httpdate
|
94
96
|
end
|
95
97
|
|
96
98
|
def duration_in_seconds
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-contrib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- rack-devel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -28,16 +28,22 @@ dependencies:
|
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.0'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '3'
|
34
37
|
type: :development
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
38
|
-
- - "
|
41
|
+
- - ">="
|
39
42
|
- !ruby/object:Gem::Version
|
40
43
|
version: '1.0'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3'
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: git-version-bump
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -224,6 +230,20 @@ dependencies:
|
|
224
230
|
- - "~>"
|
225
231
|
- !ruby/object:Gem::Version
|
226
232
|
version: '0.17'
|
233
|
+
- !ruby/object:Gem::Dependency
|
234
|
+
name: timecop
|
235
|
+
requirement: !ruby/object:Gem::Requirement
|
236
|
+
requirements:
|
237
|
+
- - "~>"
|
238
|
+
- !ruby/object:Gem::Version
|
239
|
+
version: '0.9'
|
240
|
+
type: :development
|
241
|
+
prerelease: false
|
242
|
+
version_requirements: !ruby/object:Gem::Requirement
|
243
|
+
requirements:
|
244
|
+
- - "~>"
|
245
|
+
- !ruby/object:Gem::Version
|
246
|
+
version: '0.9'
|
227
247
|
description: Contributed Rack Middleware and Utilities
|
228
248
|
email: rack-devel@googlegroups.com
|
229
249
|
executables: []
|
@@ -250,6 +270,7 @@ files:
|
|
250
270
|
- lib/rack/contrib/expectation_cascade.rb
|
251
271
|
- lib/rack/contrib/garbagecollector.rb
|
252
272
|
- lib/rack/contrib/host_meta.rb
|
273
|
+
- lib/rack/contrib/json_body_parser.rb
|
253
274
|
- lib/rack/contrib/jsonp.rb
|
254
275
|
- lib/rack/contrib/lazy_conditional_get.rb
|
255
276
|
- lib/rack/contrib/lighttpd_script_name_fix.rb
|
@@ -296,8 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
296
317
|
- !ruby/object:Gem::Version
|
297
318
|
version: '0'
|
298
319
|
requirements: []
|
299
|
-
|
300
|
-
rubygems_version: 2.7.7
|
320
|
+
rubygems_version: 3.0.3
|
301
321
|
signing_key:
|
302
322
|
specification_version: 2
|
303
323
|
summary: Contributed Rack Middleware and Utilities
|