rack-contrib 1.8.0 → 2.3.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 +5 -5
- data/README.md +17 -10
- data/lib/rack/contrib.rb +3 -3
- data/lib/rack/contrib/access.rb +6 -4
- data/lib/rack/contrib/backstage.rb +3 -1
- data/lib/rack/contrib/bounce_favicon.rb +2 -0
- data/lib/rack/contrib/callbacks.rb +2 -0
- data/lib/rack/contrib/common_cookies.rb +16 -11
- data/lib/rack/contrib/config.rb +3 -15
- data/lib/rack/contrib/cookies.rb +2 -0
- data/lib/rack/contrib/csshttprequest.rb +10 -6
- data/lib/rack/contrib/deflect.rb +34 -32
- data/lib/rack/contrib/enforce_valid_encoding.rb +2 -0
- data/lib/rack/contrib/evil.rb +2 -0
- data/lib/rack/contrib/expectation_cascade.rb +3 -1
- data/lib/rack/contrib/garbagecollector.rb +2 -0
- data/lib/rack/contrib/host_meta.rb +2 -0
- data/lib/rack/contrib/json_body_parser.rb +85 -0
- data/lib/rack/contrib/jsonp.rb +8 -6
- data/lib/rack/contrib/lazy_conditional_get.rb +9 -0
- data/lib/rack/contrib/lighttpd_script_name_fix.rb +2 -0
- data/lib/rack/contrib/locale.rb +65 -30
- data/lib/rack/contrib/mailexceptions.rb +4 -2
- data/lib/rack/contrib/nested_params.rb +6 -121
- data/lib/rack/contrib/not_found.rb +3 -1
- data/lib/rack/contrib/post_body_content_type_parser.rb +49 -4
- data/lib/rack/contrib/printout.rb +2 -0
- data/lib/rack/contrib/proctitle.rb +2 -0
- data/lib/rack/contrib/profiler.rb +6 -1
- data/lib/rack/contrib/relative_redirect.rb +10 -5
- data/lib/rack/contrib/response_cache.rb +22 -11
- data/lib/rack/contrib/response_headers.rb +2 -0
- data/lib/rack/contrib/route_exceptions.rb +2 -0
- data/lib/rack/contrib/runtime.rb +3 -30
- data/lib/rack/contrib/signals.rb +6 -0
- data/lib/rack/contrib/simple_endpoint.rb +3 -1
- data/lib/rack/contrib/static_cache.rb +17 -8
- data/lib/rack/contrib/time_zone.rb +2 -0
- data/lib/rack/contrib/try_static.rb +2 -0
- metadata +50 -32
- data/lib/rack/contrib/accept_format.rb +0 -66
- data/lib/rack/contrib/sendfile.rb +0 -138
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cf14e9cb70d9701bc1f3fe5ce036a0667f4ba6c9411e0e6ded73ef958228ec45
|
4
|
+
data.tar.gz: d0c2abe31908dd0d49dec3635cca17c720c94d098a04e9485adf917c469e0ee5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f352f5eac5008e79d645460cd9b17dcc7231fe55edb63a0540c6c0991c06ba5f989cd70c23453e854a8189bcd02ad1be96366bb4d24efdaccefc5fe823a9eed5
|
7
|
+
data.tar.gz: 379d6d0190a63d1df9c615e2fdd53bfebcac0cf506407e1e8a11c734ffc955f066a2aa56e79db21458b62efbb6603e1749f1bdddaed1c35fb84a69cd494dba8f
|
data/README.md
CHANGED
@@ -3,17 +3,16 @@
|
|
3
3
|
This package includes a variety of add-on components for Rack, a Ruby web server
|
4
4
|
interface:
|
5
5
|
|
6
|
-
* `Rack::AcceptFormat` - Adds a format extension at the end of the URI when there is none, corresponding to the mime-type given in the Accept HTTP header.
|
7
6
|
* `Rack::Access` - Limits access based on IP address
|
8
7
|
* `Rack::Backstage` - Returns content of specified file if it exists, which makes it convenient for putting up maintenance pages.
|
9
8
|
* `Rack::BounceFavicon` - Returns a 404 for requests to `/favicon.ico`
|
10
9
|
* `Rack::CSSHTTPRequest` - Adds CSSHTTPRequest support by encoding responses as CSS for cross-site AJAX-style data loading
|
11
10
|
* `Rack::Callbacks` - Implements DSL for pure before/after filter like Middlewares.
|
12
|
-
* `Rack::Config` - Shared configuration for cooperative middleware.
|
13
11
|
* `Rack::Cookies` - Adds simple cookie jar hash to env
|
14
12
|
* `Rack::Deflect` - Helps protect against DoS attacks.
|
15
13
|
* `Rack::Evil` - Lets the rack application return a response to the client from any place.
|
16
14
|
* `Rack::HostMeta` - Configures `/host-meta` using a block
|
15
|
+
* `Rack::JSONBodyParser` - Adds JSON request bodies to the Rack parameters hash.
|
17
16
|
* `Rack::JSONP` - Adds JSON-P support by stripping out the callback param and padding the response with the appropriate callback format.
|
18
17
|
* `Rack::LazyConditionalGet` - Caches a global `Last-Modified` date and updates it each time there is a request that is not `GET` or `HEAD`.
|
19
18
|
* `Rack::LighttpdScriptNameFix` - Fixes how lighttpd sets the `SCRIPT_NAME` and `PATH_INFO` variables in certain configurations.
|
@@ -21,14 +20,13 @@ interface:
|
|
21
20
|
* `Rack::MailExceptions` - Rescues exceptions raised from the app and sends a useful email with the exception, stacktrace, and contents of the environment.
|
22
21
|
* `Rack::NestedParams` - parses form params with subscripts (e.g., * "`post[title]=Hello`") into a nested/recursive Hash structure (based on Rails' implementation).
|
23
22
|
* `Rack::NotFound` - A default 404 application.
|
24
|
-
* `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
|
23
|
+
* `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
|
25
24
|
* `Rack::Printout` - Prints the environment and the response per request
|
26
25
|
* `Rack::ProcTitle` - Displays request information in process title (`$0`) for monitoring/inspection with ps(1).
|
27
26
|
* `Rack::Profiler` - Uses ruby-prof to measure request time.
|
28
27
|
* `Rack::RelativeRedirect` - Transforms relative paths in redirects to absolute URLs.
|
29
|
-
* `Rack::ResponseCache` - Caches responses to requests without query strings to Disk or a user
|
28
|
+
* `Rack::ResponseCache` - Caches responses to requests without query strings to Disk or a user provided Ruby object. Similar to Rails' page caching.
|
30
29
|
* `Rack::ResponseHeaders` - Manipulates response headers object at runtime
|
31
|
-
* `Rack::Sendfile` - Enables `X-Sendfile` support for bodies that can be served from file.
|
32
30
|
* `Rack::Signals` - Installs signal handlers that are safely processed after a request
|
33
31
|
* `Rack::SimpleEndpoint` - Creates simple endpoints with routing rules, similar to Sinatra actions
|
34
32
|
* `Rack::StaticCache` - Modifies the response headers to facilitiate client and proxy caching for static files that minimizes http requests and improves overall load times for second time visitors.
|
@@ -61,6 +59,17 @@ use Rack::MailExceptions
|
|
61
59
|
run theapp
|
62
60
|
```
|
63
61
|
|
62
|
+
#### Versioning
|
63
|
+
|
64
|
+
This package is [semver compliant](https://semver.org); you should use a
|
65
|
+
pessimistic version constraint (`~>`) against the relevant `2.x` version of
|
66
|
+
this gem.
|
67
|
+
|
68
|
+
This version of `rack-contrib` is only compatible with `rack` 2.x. If you
|
69
|
+
are using `rack` 1.x, you will need to use `rack-contrib` 1.x. A suitable
|
70
|
+
pessimistic version constraint (`~>`) is recommended.
|
71
|
+
|
72
|
+
|
64
73
|
### Testing
|
65
74
|
|
66
75
|
To contribute to the project, begin by cloning the repo and installing the necessary gems:
|
@@ -93,8 +102,6 @@ guidelines in CONTRIBUTING.md.
|
|
93
102
|
|
94
103
|
### Links
|
95
104
|
|
96
|
-
* rack-contrib on GitHub:: <
|
97
|
-
* Rack:: <
|
98
|
-
* Rack On GitHub:: <
|
99
|
-
* rack-devel mailing list:: <http://groups.google.com/group/rack-devel>
|
100
|
-
* [](https://gitter.im/rack/rack-contrib?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
105
|
+
* rack-contrib on GitHub:: <https://github.com/rack/rack-contrib>
|
106
|
+
* Rack:: <https://rack.github.io/>
|
107
|
+
* Rack On GitHub:: <https://github.com/rack/rack>
|
data/lib/rack/contrib.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rack'
|
2
4
|
|
3
5
|
module Rack
|
@@ -14,7 +16,6 @@ module Rack
|
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
|
-
autoload :AcceptFormat, "rack/contrib/accept_format"
|
18
19
|
autoload :Access, "rack/contrib/access"
|
19
20
|
autoload :BounceFavicon, "rack/contrib/bounce_favicon"
|
20
21
|
autoload :Cookies, "rack/contrib/cookies"
|
@@ -25,6 +26,7 @@ module Rack
|
|
25
26
|
autoload :HostMeta, "rack/contrib/host_meta"
|
26
27
|
autoload :GarbageCollector, "rack/contrib/garbagecollector"
|
27
28
|
autoload :JSONP, "rack/contrib/jsonp"
|
29
|
+
autoload :JSONBodyParser, "rack/contrib/json_body_parser"
|
28
30
|
autoload :LazyConditionalGet, "rack/contrib/lazy_conditional_get"
|
29
31
|
autoload :LighttpdScriptNameFix, "rack/contrib/lighttpd_script_name_fix"
|
30
32
|
autoload :Locale, "rack/contrib/locale"
|
@@ -33,8 +35,6 @@ module Rack
|
|
33
35
|
autoload :ProcTitle, "rack/contrib/proctitle"
|
34
36
|
autoload :Profiler, "rack/contrib/profiler"
|
35
37
|
autoload :ResponseHeaders, "rack/contrib/response_headers"
|
36
|
-
autoload :Runtime, "rack/contrib/runtime"
|
37
|
-
autoload :Sendfile, "rack/contrib/sendfile"
|
38
38
|
autoload :Signals, "rack/contrib/signals"
|
39
39
|
autoload :SimpleEndpoint, "rack/contrib/simple_endpoint"
|
40
40
|
autoload :TimeZone, "rack/contrib/time_zone"
|
data/lib/rack/contrib/access.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "ipaddr"
|
2
4
|
|
3
5
|
module Rack
|
@@ -48,9 +50,9 @@ module Rack
|
|
48
50
|
end
|
49
51
|
|
50
52
|
def call(env)
|
51
|
-
|
53
|
+
request = Request.new(env)
|
52
54
|
ipmasks = ipmasks_for_path(env)
|
53
|
-
return forbidden! unless ip_authorized?(ipmasks)
|
55
|
+
return forbidden! unless ip_authorized?(request, ipmasks)
|
54
56
|
status, headers, body = @app.call(env)
|
55
57
|
[status, headers, body]
|
56
58
|
end
|
@@ -73,11 +75,11 @@ module Rack
|
|
73
75
|
[403, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, []]
|
74
76
|
end
|
75
77
|
|
76
|
-
def ip_authorized?(ipmasks)
|
78
|
+
def ip_authorized?(request, ipmasks)
|
77
79
|
return true if ipmasks.nil?
|
78
80
|
|
79
81
|
ipmasks.any? do |ip_mask|
|
80
|
-
ip_mask.include?(IPAddr.new(
|
82
|
+
ip_mask.include?(IPAddr.new(request.ip))
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class Backstage
|
3
5
|
File = ::File
|
@@ -10,7 +12,7 @@ module Rack
|
|
10
12
|
def call(env)
|
11
13
|
if File.exists?(@file)
|
12
14
|
content = File.read(@file)
|
13
|
-
length =
|
15
|
+
length = content.bytesize.to_s
|
14
16
|
[503, {'Content-Type' => 'text/html', 'Content-Length' => length}, [content]]
|
15
17
|
else
|
16
18
|
@app.call(env)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
# Rack middleware to use common cookies across domain and subdomains.
|
3
5
|
class CommonCookies
|
@@ -10,21 +12,24 @@ module Rack
|
|
10
12
|
end
|
11
13
|
|
12
14
|
def call(env)
|
13
|
-
@app.call(env)
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
status, headers, body = @app.call(env)
|
16
|
+
headers = Utils::HeaderHash.new(headers)
|
17
|
+
|
18
|
+
host = env['HTTP_HOST'].sub PORT, ''
|
19
|
+
share_cookie(headers, host)
|
20
|
+
|
21
|
+
[status, headers, body]
|
17
22
|
end
|
18
23
|
|
19
24
|
private
|
20
25
|
|
21
|
-
def domain
|
22
|
-
|
26
|
+
def domain(host)
|
27
|
+
host =~ DOMAIN_REGEXP
|
23
28
|
".#{$1}.#{$2}"
|
24
29
|
end
|
25
30
|
|
26
|
-
def share_cookie(headers)
|
27
|
-
headers['Set-Cookie'] &&= common_cookie(headers) if
|
31
|
+
def share_cookie(headers, host)
|
32
|
+
headers['Set-Cookie'] &&= common_cookie(headers, host) if host !~ LOCALHOST_OR_IP_REGEXP
|
28
33
|
end
|
29
34
|
|
30
35
|
def cookie(headers)
|
@@ -32,8 +37,8 @@ module Rack
|
|
32
37
|
cookies.is_a?(Array) ? cookies.join("\n") : cookies
|
33
38
|
end
|
34
39
|
|
35
|
-
def common_cookie(headers)
|
36
|
-
cookie(headers).gsub(/; domain=[^;]*/, '').gsub(/$/, "; domain=#{domain}")
|
40
|
+
def common_cookie(headers, host)
|
41
|
+
cookie(headers).gsub(/; domain=[^;]*/, '').gsub(/$/, "; domain=#{domain(host)}")
|
37
42
|
end
|
38
43
|
end
|
39
|
-
end
|
44
|
+
end
|
data/lib/rack/contrib/config.rb
CHANGED
@@ -1,16 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
class Config
|
6
|
-
def initialize(app, &block)
|
7
|
-
@app = app
|
8
|
-
@block = block
|
9
|
-
end
|
10
|
-
|
11
|
-
def call(env)
|
12
|
-
@block.call(env)
|
13
|
-
@app.call(env)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
3
|
+
require 'rack'
|
4
|
+
require 'rack/config'
|
data/lib/rack/contrib/cookies.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'csshttprequest'
|
2
4
|
|
3
5
|
module Rack
|
@@ -13,9 +15,12 @@ module Rack
|
|
13
15
|
# the CSSHTTPRequest encoder
|
14
16
|
def call(env)
|
15
17
|
status, headers, response = @app.call(env)
|
18
|
+
headers = Utils::HeaderHash.new(headers)
|
19
|
+
|
16
20
|
if chr_request?(env)
|
17
|
-
|
18
|
-
modify_headers!(headers,
|
21
|
+
encoded_response = encode(response)
|
22
|
+
modify_headers!(headers, encoded_response)
|
23
|
+
response = [encoded_response]
|
19
24
|
end
|
20
25
|
[status, headers, response]
|
21
26
|
end
|
@@ -25,13 +30,12 @@ module Rack
|
|
25
30
|
!(/\.chr$/.match(env['PATH_INFO'])).nil? || Rack::Request.new(env).params['_format'] == 'chr'
|
26
31
|
end
|
27
32
|
|
28
|
-
def encode(
|
29
|
-
|
30
|
-
return ::CSSHTTPRequest.encode(assembled_body)
|
33
|
+
def encode(body)
|
34
|
+
::CSSHTTPRequest.encode(body.to_enum.to_a.join)
|
31
35
|
end
|
32
36
|
|
33
37
|
def modify_headers!(headers, encoded_response)
|
34
|
-
headers['Content-Length'] = encoded_response.
|
38
|
+
headers['Content-Length'] = encoded_response.bytesize.to_s
|
35
39
|
headers['Content-Type'] = 'text/css'
|
36
40
|
nil
|
37
41
|
end
|
data/lib/rack/contrib/deflect.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thread'
|
2
4
|
|
3
5
|
# TODO: optional stats
|
@@ -67,10 +69,10 @@ module Rack
|
|
67
69
|
end
|
68
70
|
|
69
71
|
def deflect? env
|
70
|
-
|
71
|
-
return false if options[:whitelist].include?
|
72
|
-
return true if options[:blacklist].include?
|
73
|
-
sync { watch }
|
72
|
+
remote_addr = env['REMOTE_ADDR']
|
73
|
+
return false if options[:whitelist].include? remote_addr
|
74
|
+
return true if options[:blacklist].include? remote_addr
|
75
|
+
sync { watch(remote_addr) }
|
74
76
|
end
|
75
77
|
|
76
78
|
def log message
|
@@ -82,55 +84,55 @@ module Rack
|
|
82
84
|
@mutex.synchronize(&block)
|
83
85
|
end
|
84
86
|
|
85
|
-
def map
|
86
|
-
@remote_addr_map[
|
87
|
+
def map(remote_addr)
|
88
|
+
@remote_addr_map[remote_addr] ||= {
|
87
89
|
:expires => Time.now + options[:interval],
|
88
90
|
:requests => 0
|
89
91
|
}
|
90
92
|
end
|
91
93
|
|
92
|
-
def watch
|
93
|
-
increment_requests
|
94
|
-
clear! if watch_expired? and not blocked?
|
95
|
-
clear! if blocked? and block_expired?
|
96
|
-
block! if watching? and exceeded_request_threshold?
|
97
|
-
blocked?
|
94
|
+
def watch(remote_addr)
|
95
|
+
increment_requests(remote_addr)
|
96
|
+
clear!(remote_addr) if watch_expired?(remote_addr) and not blocked?(remote_addr)
|
97
|
+
clear!(remote_addr) if blocked?(remote_addr) and block_expired?(remote_addr)
|
98
|
+
block!(remote_addr) if watching?(remote_addr) and exceeded_request_threshold?(remote_addr)
|
99
|
+
blocked?(remote_addr)
|
98
100
|
end
|
99
101
|
|
100
|
-
def block!
|
101
|
-
return if blocked?
|
102
|
-
log "blocked #{
|
103
|
-
map[:block_expires] = Time.now + options[:block_duration]
|
102
|
+
def block!(remote_addr)
|
103
|
+
return if blocked?(remote_addr)
|
104
|
+
log "blocked #{remote_addr}"
|
105
|
+
map(remote_addr)[:block_expires] = Time.now + options[:block_duration]
|
104
106
|
end
|
105
107
|
|
106
|
-
def blocked?
|
107
|
-
map.has_key? :block_expires
|
108
|
+
def blocked?(remote_addr)
|
109
|
+
map(remote_addr).has_key? :block_expires
|
108
110
|
end
|
109
111
|
|
110
|
-
def block_expired?
|
111
|
-
map[:block_expires] < Time.now rescue false
|
112
|
+
def block_expired?(remote_addr)
|
113
|
+
map(remote_addr)[:block_expires] < Time.now rescue false
|
112
114
|
end
|
113
115
|
|
114
|
-
def watching?
|
115
|
-
@remote_addr_map.has_key?
|
116
|
+
def watching?(remote_addr)
|
117
|
+
@remote_addr_map.has_key? remote_addr
|
116
118
|
end
|
117
119
|
|
118
|
-
def clear!
|
119
|
-
return unless watching?
|
120
|
-
log "released #{
|
121
|
-
@remote_addr_map.delete
|
120
|
+
def clear!(remote_addr)
|
121
|
+
return unless watching?(remote_addr)
|
122
|
+
log "released #{remote_addr}" if blocked?(remote_addr)
|
123
|
+
@remote_addr_map.delete remote_addr
|
122
124
|
end
|
123
125
|
|
124
|
-
def increment_requests
|
125
|
-
map[:requests] += 1
|
126
|
+
def increment_requests(remote_addr)
|
127
|
+
map(remote_addr)[:requests] += 1
|
126
128
|
end
|
127
129
|
|
128
|
-
def exceeded_request_threshold?
|
129
|
-
map[:requests] > options[:request_threshold]
|
130
|
+
def exceeded_request_threshold?(remote_addr)
|
131
|
+
map(remote_addr)[:requests] > options[:request_threshold]
|
130
132
|
end
|
131
133
|
|
132
|
-
def watch_expired?
|
133
|
-
map[:expires] <= Time.now
|
134
|
+
def watch_expired?(remote_addr)
|
135
|
+
map(remote_addr)[:expires] <= Time.now
|
134
136
|
end
|
135
137
|
|
136
138
|
end
|
data/lib/rack/contrib/evil.rb
CHANGED
@@ -0,0 +1,85 @@
|
|
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
|
+
begin
|
59
|
+
if @verbs.include?(env[Rack::REQUEST_METHOD]) &&
|
60
|
+
@matcher.call(@media, env['CONTENT_TYPE'])
|
61
|
+
|
62
|
+
update_form_hash_with_json_body(env)
|
63
|
+
end
|
64
|
+
rescue JSON::ParserError
|
65
|
+
body = { error: 'Failed to parse body as JSON' }.to_json
|
66
|
+
header = { 'Content-Type' => 'application/json' }
|
67
|
+
return Rack::Response.new(body, 400, header).finish
|
68
|
+
end
|
69
|
+
@app.call(env)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def update_form_hash_with_json_body(env)
|
75
|
+
body = env[Rack::RACK_INPUT]
|
76
|
+
return unless (body_content = body.read) && !body_content.empty?
|
77
|
+
|
78
|
+
body.rewind # somebody might try to read this stream
|
79
|
+
env.update(
|
80
|
+
Rack::RACK_REQUEST_FORM_HASH => @parser.call(body_content),
|
81
|
+
Rack::RACK_REQUEST_FORM_INPUT => body
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|