actionpack 4.2.10 → 7.2.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +86 -600
- data/MIT-LICENSE +1 -1
- data/README.rdoc +13 -14
- data/lib/abstract_controller/asset_paths.rb +5 -1
- data/lib/abstract_controller/base.rb +166 -136
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +126 -57
- data/lib/abstract_controller/collector.rb +13 -15
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +181 -132
- data/lib/abstract_controller/logger.rb +5 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
- data/lib/abstract_controller/rendering.rb +56 -56
- data/lib/abstract_controller/translation.rb +29 -15
- data/lib/abstract_controller/url_for.rb +15 -11
- data/lib/abstract_controller.rb +21 -5
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +154 -0
- data/lib/action_controller/base.rb +219 -155
- data/lib/action_controller/caching.rb +28 -68
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +35 -22
- data/lib/action_controller/metal/allow_browser.rb +119 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +259 -122
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +9 -5
- data/lib/action_controller/metal/data_streaming.rb +87 -104
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
- data/lib/action_controller/metal/exceptions.rb +71 -24
- data/lib/action_controller/metal/flash.rb +26 -19
- data/lib/action_controller/metal/head.rb +45 -36
- data/lib/action_controller/metal/helpers.rb +80 -64
- data/lib/action_controller/metal/http_authentication.rb +297 -244
- data/lib/action_controller/metal/implicit_render.rb +57 -9
- data/lib/action_controller/metal/instrumentation.rb +76 -64
- data/lib/action_controller/metal/live.rb +238 -176
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +177 -166
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +145 -118
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +203 -64
- data/lib/action_controller/metal/renderers.rb +108 -65
- data/lib/action_controller/metal/rendering.rb +216 -56
- data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
- data/lib/action_controller/metal/rescue.rb +19 -21
- data/lib/action_controller/metal/streaming.rb +179 -138
- data/lib/action_controller/metal/strong_parameters.rb +1058 -382
- data/lib/action_controller/metal/testing.rb +11 -17
- data/lib/action_controller/metal/url_for.rb +37 -21
- data/lib/action_controller/metal.rb +236 -138
- data/lib/action_controller/railtie.rb +89 -11
- data/lib/action_controller/railties/helpers.rb +5 -1
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +425 -497
- data/lib/action_controller.rb +44 -22
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +119 -63
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +364 -0
- data/lib/action_dispatch/http/filter_parameters.rb +36 -34
- data/lib/action_dispatch/http/filter_redirect.rb +24 -12
- data/lib/action_dispatch/http/headers.rb +66 -31
- data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
- data/lib/action_dispatch/http/mime_type.rb +196 -136
- data/lib/action_dispatch/http/mime_types.rb +25 -7
- data/lib/action_dispatch/http/parameters.rb +97 -45
- data/lib/action_dispatch/http/permissions_policy.rb +187 -0
- data/lib/action_dispatch/http/rack_cache.rb +6 -0
- data/lib/action_dispatch/http/request.rb +299 -170
- data/lib/action_dispatch/http/response.rb +311 -160
- data/lib/action_dispatch/http/upload.rb +52 -23
- data/lib/action_dispatch/http/url.rb +201 -125
- data/lib/action_dispatch/journey/formatter.rb +110 -50
- data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
- data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
- data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
- data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
- data/lib/action_dispatch/journey/nodes/node.rb +100 -20
- data/lib/action_dispatch/journey/parser.rb +19 -17
- data/lib/action_dispatch/journey/parser.y +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +14 -4
- data/lib/action_dispatch/journey/path/pattern.rb +79 -63
- data/lib/action_dispatch/journey/route.rb +108 -44
- data/lib/action_dispatch/journey/router/utils.rb +41 -29
- data/lib/action_dispatch/journey/router.rb +64 -57
- data/lib/action_dispatch/journey/routes.rb +23 -21
- data/lib/action_dispatch/journey/scanner.rb +28 -17
- data/lib/action_dispatch/journey/visitors.rb +100 -54
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/journey.rb +7 -5
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +7 -6
- data/lib/action_dispatch/middleware/cookies.rb +471 -328
- data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +143 -101
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
- data/lib/action_dispatch/middleware/reloader.rb +10 -92
- data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
- data/lib/action_dispatch/middleware/request_id.rb +29 -15
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
- data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
- data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
- data/lib/action_dispatch/middleware/ssl.rb +134 -36
- data/lib/action_dispatch/middleware/stack.rb +109 -44
- data/lib/action_dispatch/middleware/static.rb +159 -90
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
- data/lib/action_dispatch/railtie.rb +44 -16
- data/lib/action_dispatch/request/session.rb +159 -69
- data/lib/action_dispatch/request/utils.rb +97 -23
- data/lib/action_dispatch/routing/endpoint.rb +11 -2
- data/lib/action_dispatch/routing/inspector.rb +195 -106
- data/lib/action_dispatch/routing/mapper.rb +1338 -955
- data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
- data/lib/action_dispatch/routing/redirection.rb +78 -51
- data/lib/action_dispatch/routing/route_set.rb +460 -374
- data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
- data/lib/action_dispatch/routing/url_for.rb +172 -124
- data/lib/action_dispatch/routing.rb +159 -158
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +84 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +71 -39
- data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
- data/lib/action_dispatch/testing/assertions.rb +9 -6
- data/lib/action_dispatch/testing/integration.rb +486 -306
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +35 -22
- data/lib/action_dispatch/testing/test_request.rb +29 -34
- data/lib/action_dispatch/testing/test_response.rb +48 -15
- data/lib/action_dispatch.rb +82 -40
- data/lib/action_pack/gem_version.rb +8 -4
- data/lib/action_pack/version.rb +6 -2
- data/lib/action_pack.rb +21 -18
- metadata +146 -56
- data/lib/action_controller/caching/fragments.rb +0 -103
- data/lib/action_controller/metal/force_ssl.rb +0 -97
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/http/parameter_filter.rb +0 -72
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,72 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
1
5
|
module ActionDispatch
|
6
|
+
# # Action Dispatch SSL
|
7
|
+
#
|
8
|
+
# This middleware is added to the stack when `config.force_ssl = true`, and is
|
9
|
+
# passed the options set in `config.ssl_options`. It does three jobs to enforce
|
10
|
+
# secure HTTP requests:
|
11
|
+
#
|
12
|
+
# 1. **TLS redirect**: Permanently redirects `http://` requests to `https://`
|
13
|
+
# with the same URL host, path, etc. Enabled by default. Set
|
14
|
+
# `config.ssl_options` to modify the destination URL (e.g. `redirect: {
|
15
|
+
# host: "secure.widgets.com", port: 8080 }`), or set `redirect: false` to
|
16
|
+
# disable this feature.
|
17
|
+
#
|
18
|
+
# Requests can opt-out of redirection with `exclude`:
|
19
|
+
#
|
20
|
+
# config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } }
|
21
|
+
#
|
22
|
+
# Cookies will not be flagged as secure for excluded requests.
|
23
|
+
#
|
24
|
+
# 2. **Secure cookies**: Sets the `secure` flag on cookies to tell browsers
|
25
|
+
# they must not be sent along with `http://` requests. Enabled by default.
|
26
|
+
# Set `config.ssl_options` with `secure_cookies: false` to disable this
|
27
|
+
# feature.
|
28
|
+
#
|
29
|
+
# 3. **HTTP Strict Transport Security (HSTS)**: Tells the browser to remember
|
30
|
+
# this site as TLS-only and automatically redirect non-TLS requests. Enabled
|
31
|
+
# by default. Configure `config.ssl_options` with `hsts: false` to disable.
|
32
|
+
#
|
33
|
+
# Set `config.ssl_options` with `hsts: { ... }` to configure HSTS:
|
34
|
+
#
|
35
|
+
# * `expires`: How long, in seconds, these settings will stick. The
|
36
|
+
# minimum required to qualify for browser preload lists is 1 year.
|
37
|
+
# Defaults to 2 years (recommended).
|
38
|
+
#
|
39
|
+
# * `subdomains`: Set to `true` to tell the browser to apply these
|
40
|
+
# settings to all subdomains. This protects your cookies from
|
41
|
+
# interception by a vulnerable site on a subdomain. Defaults to `true`.
|
42
|
+
#
|
43
|
+
# * `preload`: Advertise that this site may be included in browsers'
|
44
|
+
# preloaded HSTS lists. HSTS protects your site on every visit *except
|
45
|
+
# the first visit* since it hasn't seen your HSTS header yet. To close
|
46
|
+
# this gap, browser vendors include a baked-in list of HSTS-enabled
|
47
|
+
# sites. Go to https://hstspreload.org to submit your site for
|
48
|
+
# inclusion. Defaults to `false`.
|
49
|
+
#
|
50
|
+
#
|
51
|
+
# To turn off HSTS, omitting the header is not enough. Browsers will
|
52
|
+
# remember the original HSTS directive until it expires. Instead, use the
|
53
|
+
# header to tell browsers to expire HSTS immediately. Setting `hsts: false`
|
54
|
+
# is a shortcut for `hsts: { expires: 0 }`.
|
55
|
+
#
|
2
56
|
class SSL
|
3
|
-
|
57
|
+
# :stopdoc: Default to 2 years as recommended on hstspreload.org.
|
58
|
+
HSTS_EXPIRES_IN = 63072000
|
59
|
+
|
60
|
+
PERMANENT_REDIRECT_REQUEST_METHODS = %w[GET HEAD] # :nodoc:
|
4
61
|
|
5
62
|
def self.default_hsts_options
|
6
|
-
{ :
|
63
|
+
{ expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
|
7
64
|
end
|
8
65
|
|
9
|
-
def initialize(app,
|
66
|
+
def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, ssl_default_redirect_status: nil)
|
10
67
|
@app = app
|
11
68
|
|
12
|
-
@
|
13
|
-
|
14
|
-
@
|
69
|
+
@redirect = redirect
|
70
|
+
|
71
|
+
@exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
|
72
|
+
@secure_cookies = secure_cookies
|
15
73
|
|
16
|
-
@
|
17
|
-
@
|
74
|
+
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
|
75
|
+
@ssl_default_redirect_status = ssl_default_redirect_status
|
18
76
|
end
|
19
77
|
|
20
78
|
def call(env)
|
21
|
-
request = Request.new
|
79
|
+
request = Request.new env
|
22
80
|
|
23
81
|
if request.ssl?
|
24
|
-
status, headers, body
|
25
|
-
|
26
|
-
|
27
|
-
|
82
|
+
@app.call(env).tap do |status, headers, body|
|
83
|
+
set_hsts_header! headers
|
84
|
+
flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request)
|
85
|
+
end
|
28
86
|
else
|
29
|
-
redirect_to_https(request)
|
87
|
+
return redirect_to_https request unless @exclude.call(request)
|
88
|
+
@app.call(env)
|
30
89
|
end
|
31
90
|
end
|
32
91
|
|
33
92
|
private
|
34
|
-
def
|
35
|
-
|
36
|
-
port = @port || request.port
|
37
|
-
|
38
|
-
location = "https://#{host}"
|
39
|
-
location << ":#{port}" if port != 80
|
40
|
-
location << request.fullpath
|
41
|
-
|
42
|
-
headers = { 'Content-Type' => 'text/html', 'Location' => location }
|
43
|
-
|
44
|
-
[301, headers, []]
|
93
|
+
def set_hsts_header!(headers)
|
94
|
+
headers[Constants::STRICT_TRANSPORT_SECURITY] ||= @hsts_header
|
45
95
|
end
|
46
96
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
97
|
+
def normalize_hsts_options(options)
|
98
|
+
case options
|
99
|
+
# Explicitly disabling HSTS clears the existing setting from browsers by setting
|
100
|
+
# expiry to 0.
|
101
|
+
when false
|
102
|
+
self.class.default_hsts_options.merge(expires: 0)
|
103
|
+
# Default to enabled, with default options.
|
104
|
+
when nil, true
|
105
|
+
self.class.default_hsts_options
|
53
106
|
else
|
54
|
-
|
107
|
+
self.class.default_hsts_options.merge(options)
|
55
108
|
end
|
56
109
|
end
|
57
110
|
|
111
|
+
# https://tools.ietf.org/html/rfc6797#section-6.1
|
112
|
+
def build_hsts_header(hsts)
|
113
|
+
value = +"max-age=#{hsts[:expires].to_i}"
|
114
|
+
value << "; includeSubDomains" if hsts[:subdomains]
|
115
|
+
value << "; preload" if hsts[:preload]
|
116
|
+
value
|
117
|
+
end
|
118
|
+
|
58
119
|
def flag_cookies_as_secure!(headers)
|
59
|
-
|
60
|
-
|
120
|
+
cookies = headers[Rack::SET_COOKIE]
|
121
|
+
return unless cookies
|
61
122
|
|
62
|
-
|
63
|
-
|
123
|
+
if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
|
124
|
+
cookies = cookies.split("\n")
|
125
|
+
headers[Rack::SET_COOKIE] = cookies.map { |cookie|
|
126
|
+
if !/;\s*secure\s*(;|$)/i.match?(cookie)
|
64
127
|
"#{cookie}; secure"
|
65
128
|
else
|
66
129
|
cookie
|
67
130
|
end
|
68
131
|
}.join("\n")
|
132
|
+
else
|
133
|
+
headers[Rack::SET_COOKIE] = Array(cookies).map do |cookie|
|
134
|
+
if !/;\s*secure\s*(;|$)/i.match?(cookie)
|
135
|
+
"#{cookie}; secure"
|
136
|
+
else
|
137
|
+
cookie
|
138
|
+
end
|
139
|
+
end
|
69
140
|
end
|
70
141
|
end
|
142
|
+
|
143
|
+
def redirect_to_https(request)
|
144
|
+
[ @redirect.fetch(:status, redirection_status(request)),
|
145
|
+
{ Rack::CONTENT_TYPE => "text/html; charset=utf-8",
|
146
|
+
Constants::LOCATION => https_location_for(request) },
|
147
|
+
(@redirect[:body] || []) ]
|
148
|
+
end
|
149
|
+
|
150
|
+
def redirection_status(request)
|
151
|
+
if PERMANENT_REDIRECT_REQUEST_METHODS.include?(request.raw_request_method)
|
152
|
+
301 # Issue a permanent redirect via a GET request.
|
153
|
+
elsif @ssl_default_redirect_status
|
154
|
+
@ssl_default_redirect_status
|
155
|
+
else
|
156
|
+
307 # Issue a fresh request redirect to preserve the HTTP method.
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def https_location_for(request)
|
161
|
+
host = @redirect[:host] || request.host
|
162
|
+
port = @redirect[:port] || request.port
|
163
|
+
|
164
|
+
location = +"https://#{host}"
|
165
|
+
location << ":#{port}" if port != 80 && port != 443
|
166
|
+
location << request.fullpath
|
167
|
+
location
|
168
|
+
end
|
71
169
|
end
|
72
170
|
end
|
@@ -1,52 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
1
5
|
require "active_support/inflector/methods"
|
2
6
|
require "active_support/dependencies"
|
3
7
|
|
4
8
|
module ActionDispatch
|
9
|
+
# # Action Dispatch MiddlewareStack
|
10
|
+
#
|
11
|
+
# Read more about [Rails middleware
|
12
|
+
# stack](https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack)
|
13
|
+
# in the guides.
|
5
14
|
class MiddlewareStack
|
6
15
|
class Middleware
|
7
|
-
attr_reader :args, :block, :
|
8
|
-
|
9
|
-
def initialize(klass_or_name, *args, &block)
|
10
|
-
@klass = nil
|
11
|
-
|
12
|
-
if klass_or_name.respond_to?(:name)
|
13
|
-
@klass = klass_or_name
|
14
|
-
@name = @klass.name
|
15
|
-
else
|
16
|
-
@name = klass_or_name.to_s
|
17
|
-
end
|
16
|
+
attr_reader :args, :block, :klass
|
18
17
|
|
19
|
-
|
20
|
-
@
|
18
|
+
def initialize(klass, args, block)
|
19
|
+
@klass = klass
|
20
|
+
@args = args
|
21
|
+
@block = block
|
21
22
|
end
|
22
23
|
|
23
|
-
def klass
|
24
|
-
@klass || classcache[@name]
|
25
|
-
end
|
24
|
+
def name; klass.name; end
|
26
25
|
|
27
26
|
def ==(middleware)
|
28
27
|
case middleware
|
29
28
|
when Middleware
|
30
29
|
klass == middleware.klass
|
31
|
-
when
|
30
|
+
when Module
|
32
31
|
klass == middleware
|
33
|
-
else
|
34
|
-
normalize(@name) == normalize(middleware)
|
35
32
|
end
|
36
33
|
end
|
37
34
|
|
38
35
|
def inspect
|
39
|
-
klass.
|
36
|
+
if klass.is_a?(Module)
|
37
|
+
klass.to_s
|
38
|
+
else
|
39
|
+
klass.class.to_s
|
40
|
+
end
|
40
41
|
end
|
41
42
|
|
42
43
|
def build(app)
|
43
44
|
klass.new(app, *args, &block)
|
44
45
|
end
|
45
46
|
|
46
|
-
|
47
|
+
def build_instrumented(app)
|
48
|
+
InstrumentationProxy.new(build(app), inspect)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# This class is used to instrument the execution of a single middleware. It
|
53
|
+
# proxies the `call` method transparently and instruments the method call.
|
54
|
+
class InstrumentationProxy
|
55
|
+
EVENT_NAME = "process_middleware.action_dispatch"
|
56
|
+
|
57
|
+
def initialize(middleware, class_name)
|
58
|
+
@middleware = middleware
|
47
59
|
|
48
|
-
|
49
|
-
|
60
|
+
@payload = {
|
61
|
+
middleware: class_name,
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def call(env)
|
66
|
+
ActiveSupport::Notifications.instrument(EVENT_NAME, @payload) do
|
67
|
+
@middleware.call(env)
|
68
|
+
end
|
50
69
|
end
|
51
70
|
end
|
52
71
|
|
@@ -59,8 +78,8 @@ module ActionDispatch
|
|
59
78
|
yield(self) if block_given?
|
60
79
|
end
|
61
80
|
|
62
|
-
def each
|
63
|
-
@middlewares.each
|
81
|
+
def each(&block)
|
82
|
+
@middlewares.each(&block)
|
64
83
|
end
|
65
84
|
|
66
85
|
def size
|
@@ -75,20 +94,20 @@ module ActionDispatch
|
|
75
94
|
middlewares[i]
|
76
95
|
end
|
77
96
|
|
78
|
-
def unshift(*args, &block)
|
79
|
-
|
80
|
-
middlewares.unshift(middleware)
|
97
|
+
def unshift(klass, *args, &block)
|
98
|
+
middlewares.unshift(build_middleware(klass, args, block))
|
81
99
|
end
|
100
|
+
ruby2_keywords(:unshift)
|
82
101
|
|
83
102
|
def initialize_copy(other)
|
84
103
|
self.middlewares = other.middlewares.dup
|
85
104
|
end
|
86
105
|
|
87
|
-
def insert(index, *args, &block)
|
106
|
+
def insert(index, klass, *args, &block)
|
88
107
|
index = assert_index(index, :before)
|
89
|
-
|
90
|
-
middlewares.insert(index, middleware)
|
108
|
+
middlewares.insert(index, build_middleware(klass, args, block))
|
91
109
|
end
|
110
|
+
ruby2_keywords(:insert)
|
92
111
|
|
93
112
|
alias_method :insert_before, :insert
|
94
113
|
|
@@ -96,34 +115,80 @@ module ActionDispatch
|
|
96
115
|
index = assert_index(index, :after)
|
97
116
|
insert(index + 1, *args, &block)
|
98
117
|
end
|
118
|
+
ruby2_keywords(:insert_after)
|
99
119
|
|
100
120
|
def swap(target, *args, &block)
|
101
121
|
index = assert_index(target, :before)
|
102
122
|
insert(index, *args, &block)
|
103
123
|
middlewares.delete_at(index + 1)
|
104
124
|
end
|
125
|
+
ruby2_keywords(:swap)
|
105
126
|
|
127
|
+
# Deletes a middleware from the middleware stack.
|
128
|
+
#
|
129
|
+
# Returns the array of middlewares not including the deleted item, or returns
|
130
|
+
# nil if the target is not found.
|
106
131
|
def delete(target)
|
107
|
-
middlewares.
|
132
|
+
middlewares.reject! { |m| m.name == target.name }
|
108
133
|
end
|
109
134
|
|
110
|
-
|
111
|
-
|
112
|
-
|
135
|
+
# Deletes a middleware from the middleware stack.
|
136
|
+
#
|
137
|
+
# Returns the array of middlewares not including the deleted item, or raises
|
138
|
+
# `RuntimeError` if the target is not found.
|
139
|
+
def delete!(target)
|
140
|
+
delete(target) || (raise "No such middleware to remove: #{target.inspect}")
|
113
141
|
end
|
114
142
|
|
115
|
-
def
|
116
|
-
|
117
|
-
|
118
|
-
|
143
|
+
def move(target, source)
|
144
|
+
source_index = assert_index(source, :before)
|
145
|
+
source_middleware = middlewares.delete_at(source_index)
|
146
|
+
|
147
|
+
target_index = assert_index(target, :before)
|
148
|
+
middlewares.insert(target_index, source_middleware)
|
119
149
|
end
|
120
150
|
|
121
|
-
|
151
|
+
alias_method :move_before, :move
|
152
|
+
|
153
|
+
def move_after(target, source)
|
154
|
+
source_index = assert_index(source, :after)
|
155
|
+
source_middleware = middlewares.delete_at(source_index)
|
122
156
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
157
|
+
target_index = assert_index(target, :after)
|
158
|
+
middlewares.insert(target_index + 1, source_middleware)
|
159
|
+
end
|
160
|
+
|
161
|
+
def use(klass, *args, &block)
|
162
|
+
middlewares.push(build_middleware(klass, args, block))
|
163
|
+
end
|
164
|
+
ruby2_keywords(:use)
|
165
|
+
|
166
|
+
def build(app = nil, &block)
|
167
|
+
instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME)
|
168
|
+
middlewares.freeze.reverse.inject(app || block) do |a, e|
|
169
|
+
if instrumenting
|
170
|
+
e.build_instrumented(a)
|
171
|
+
else
|
172
|
+
e.build(a)
|
173
|
+
end
|
174
|
+
end
|
127
175
|
end
|
176
|
+
|
177
|
+
private
|
178
|
+
def assert_index(index, where)
|
179
|
+
i = index.is_a?(Integer) ? index : index_of(index)
|
180
|
+
raise "No such middleware to insert #{where}: #{index.inspect}" unless i
|
181
|
+
i
|
182
|
+
end
|
183
|
+
|
184
|
+
def build_middleware(klass, args, block)
|
185
|
+
Middleware.new(klass, args, block)
|
186
|
+
end
|
187
|
+
|
188
|
+
def index_of(klass)
|
189
|
+
middlewares.index do |m|
|
190
|
+
m.name == klass.name
|
191
|
+
end
|
192
|
+
end
|
128
193
|
end
|
129
194
|
end
|