rack-contrib 1.8.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
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
|
-
* [![Gitter](https://badges.gitter.im/Join Chat.svg)](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
|