actionpack 7.2.2.1 → 8.1.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +408 -95
- data/README.rdoc +1 -1
- data/lib/abstract_controller/asset_paths.rb +4 -2
- data/lib/abstract_controller/base.rb +12 -17
- data/lib/abstract_controller/caching.rb +6 -3
- data/lib/abstract_controller/callbacks.rb +6 -0
- data/lib/abstract_controller/collector.rb +1 -1
- data/lib/abstract_controller/helpers.rb +1 -1
- data/lib/abstract_controller/logger.rb +2 -1
- data/lib/abstract_controller/rendering.rb +0 -1
- data/lib/action_controller/api.rb +1 -0
- data/lib/action_controller/base.rb +3 -2
- data/lib/action_controller/caching.rb +1 -2
- data/lib/action_controller/form_builder.rb +4 -4
- data/lib/action_controller/log_subscriber.rb +22 -3
- data/lib/action_controller/metal/allow_browser.rb +12 -2
- data/lib/action_controller/metal/conditional_get.rb +30 -1
- data/lib/action_controller/metal/data_streaming.rb +5 -5
- data/lib/action_controller/metal/exceptions.rb +5 -0
- data/lib/action_controller/metal/flash.rb +1 -4
- data/lib/action_controller/metal/head.rb +3 -1
- data/lib/action_controller/metal/instrumentation.rb +1 -2
- data/lib/action_controller/metal/live.rb +66 -26
- data/lib/action_controller/metal/params_wrapper.rb +3 -3
- data/lib/action_controller/metal/permissions_policy.rb +9 -0
- data/lib/action_controller/metal/rate_limiting.rb +39 -9
- data/lib/action_controller/metal/redirecting.rb +109 -16
- data/lib/action_controller/metal/renderers.rb +29 -9
- data/lib/action_controller/metal/rendering.rb +8 -2
- data/lib/action_controller/metal/request_forgery_protection.rb +21 -11
- data/lib/action_controller/metal/rescue.rb +9 -0
- data/lib/action_controller/metal/streaming.rb +5 -84
- data/lib/action_controller/metal/strong_parameters.rb +277 -92
- data/lib/action_controller/railtie.rb +33 -15
- data/lib/action_controller/renderer.rb +0 -1
- data/lib/action_controller/structured_event_subscriber.rb +116 -0
- data/lib/action_controller/test_case.rb +12 -2
- data/lib/action_dispatch/constants.rb +6 -0
- data/lib/action_dispatch/http/cache.rb +138 -11
- data/lib/action_dispatch/http/content_security_policy.rb +14 -1
- data/lib/action_dispatch/http/filter_parameters.rb +5 -3
- data/lib/action_dispatch/http/mime_negotiation.rb +63 -4
- data/lib/action_dispatch/http/mime_types.rb +1 -0
- data/lib/action_dispatch/http/param_builder.rb +187 -0
- data/lib/action_dispatch/http/param_error.rb +26 -0
- data/lib/action_dispatch/http/parameters.rb +3 -3
- data/lib/action_dispatch/http/permissions_policy.rb +6 -0
- data/lib/action_dispatch/http/query_parser.rb +55 -0
- data/lib/action_dispatch/http/request.rb +73 -23
- data/lib/action_dispatch/http/response.rb +65 -17
- data/lib/action_dispatch/http/url.rb +112 -16
- data/lib/action_dispatch/journey/formatter.rb +8 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +33 -12
- data/lib/action_dispatch/journey/gtg/transition_table.rb +37 -45
- data/lib/action_dispatch/journey/nodes/node.rb +2 -1
- data/lib/action_dispatch/journey/parser.rb +99 -196
- data/lib/action_dispatch/journey/route.rb +45 -31
- data/lib/action_dispatch/journey/router/utils.rb +8 -14
- data/lib/action_dispatch/journey/router.rb +59 -81
- data/lib/action_dispatch/journey/routes.rb +7 -0
- data/lib/action_dispatch/journey/scanner.rb +44 -42
- data/lib/action_dispatch/journey/visitors.rb +55 -23
- data/lib/action_dispatch/journey/visualizer/fsm.js +4 -6
- data/lib/action_dispatch/log_subscriber.rb +7 -3
- data/lib/action_dispatch/middleware/cookies.rb +8 -4
- data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -5
- data/lib/action_dispatch/middleware/debug_view.rb +11 -5
- data/lib/action_dispatch/middleware/exception_wrapper.rb +14 -14
- data/lib/action_dispatch/middleware/executor.rb +17 -4
- data/lib/action_dispatch/middleware/public_exceptions.rb +6 -6
- data/lib/action_dispatch/middleware/remote_ip.rb +11 -5
- data/lib/action_dispatch/middleware/request_id.rb +2 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +17 -0
- data/lib/action_dispatch/middleware/ssl.rb +13 -3
- data/lib/action_dispatch/middleware/templates/rescues/_copy_button.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +3 -5
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +9 -5
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +4 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +50 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -0
- data/lib/action_dispatch/railtie.rb +21 -0
- data/lib/action_dispatch/request/session.rb +1 -0
- data/lib/action_dispatch/request/utils.rb +9 -3
- data/lib/action_dispatch/routing/inspector.rb +80 -57
- data/lib/action_dispatch/routing/mapper.rb +409 -228
- data/lib/action_dispatch/routing/polymorphic_routes.rb +2 -2
- data/lib/action_dispatch/routing/redirection.rb +10 -7
- data/lib/action_dispatch/routing/route_set.rb +21 -12
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/system_test_case.rb +3 -3
- data/lib/action_dispatch/system_testing/browser.rb +12 -21
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +2 -2
- data/lib/action_dispatch/testing/assertion_response.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +26 -2
- data/lib/action_dispatch/testing/assertions/routing.rb +27 -15
- data/lib/action_dispatch/testing/integration.rb +16 -7
- data/lib/action_dispatch/testing/request_encoder.rb +9 -9
- data/lib/action_dispatch/testing/test_process.rb +1 -2
- data/lib/action_dispatch.rb +14 -4
- data/lib/action_pack/gem_version.rb +3 -3
- metadata +19 -38
- data/lib/action_dispatch/journey/parser.y +0 -50
- data/lib/action_dispatch/journey/parser_extras.rb +0 -33
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
# :markup: markdown
|
|
4
4
|
|
|
5
5
|
require "abstract_controller/error"
|
|
6
|
-
require "active_support/configurable"
|
|
7
6
|
require "active_support/descendants_tracker"
|
|
8
7
|
require "active_support/core_ext/module/anonymous"
|
|
9
8
|
require "active_support/core_ext/module/attr_internal"
|
|
@@ -47,7 +46,7 @@ module AbstractController
|
|
|
47
46
|
# Returns the formats that can be processed by the controller.
|
|
48
47
|
attr_internal :formats
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
class_attribute :config, instance_predicate: false, default: ActiveSupport::OrderedOptions.new
|
|
51
50
|
extend ActiveSupport::DescendantsTracker
|
|
52
51
|
|
|
53
52
|
class << self
|
|
@@ -65,6 +64,7 @@ module AbstractController
|
|
|
65
64
|
unless klass.instance_variable_defined?(:@abstract)
|
|
66
65
|
klass.instance_variable_set(:@abstract, false)
|
|
67
66
|
end
|
|
67
|
+
klass.config = ActiveSupport::InheritableOptions.new(config)
|
|
68
68
|
super
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -86,22 +86,16 @@ module AbstractController
|
|
|
86
86
|
controller.public_instance_methods(true) - methods
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
# A
|
|
89
|
+
# A `Set` of method names that should be considered actions. This includes all
|
|
90
90
|
# public instance methods on a controller, less any internal methods (see
|
|
91
91
|
# internal_methods), adding back in any methods that are internal, but still
|
|
92
92
|
# exist on the class itself.
|
|
93
|
-
#
|
|
94
|
-
# #### Returns
|
|
95
|
-
# * `Set` - A set of all methods that should be considered actions.
|
|
96
|
-
#
|
|
97
93
|
def action_methods
|
|
98
94
|
@action_methods ||= begin
|
|
99
95
|
# All public instance methods of this class, including ancestors except for
|
|
100
96
|
# public instance methods of Base and its ancestors.
|
|
101
97
|
methods = public_instance_methods(true) - internal_methods
|
|
102
|
-
|
|
103
|
-
methods.concat(public_instance_methods(false))
|
|
104
|
-
methods.map!(&:to_s)
|
|
98
|
+
methods.map!(&:name)
|
|
105
99
|
methods.to_set
|
|
106
100
|
end
|
|
107
101
|
end
|
|
@@ -121,13 +115,14 @@ module AbstractController
|
|
|
121
115
|
#
|
|
122
116
|
# MyApp::MyPostsController.controller_path # => "my_app/my_posts"
|
|
123
117
|
#
|
|
124
|
-
# #### Returns
|
|
125
|
-
# * `String`
|
|
126
|
-
#
|
|
127
118
|
def controller_path
|
|
128
119
|
@controller_path ||= name.delete_suffix("Controller").underscore unless anonymous?
|
|
129
120
|
end
|
|
130
121
|
|
|
122
|
+
def configure # :nodoc:
|
|
123
|
+
yield config
|
|
124
|
+
end
|
|
125
|
+
|
|
131
126
|
# Refresh the cached action_methods when a new action_method is added.
|
|
132
127
|
def method_added(name)
|
|
133
128
|
super
|
|
@@ -147,10 +142,6 @@ module AbstractController
|
|
|
147
142
|
# The actual method that is called is determined by calling #method_for_action.
|
|
148
143
|
# If no method can handle the action, then an AbstractController::ActionNotFound
|
|
149
144
|
# error is raised.
|
|
150
|
-
#
|
|
151
|
-
# #### Returns
|
|
152
|
-
# * `self`
|
|
153
|
-
#
|
|
154
145
|
def process(action, ...)
|
|
155
146
|
@_action_name = action.to_s
|
|
156
147
|
|
|
@@ -201,6 +192,10 @@ module AbstractController
|
|
|
201
192
|
true
|
|
202
193
|
end
|
|
203
194
|
|
|
195
|
+
def config # :nodoc:
|
|
196
|
+
@_config ||= self.class.config.inheritable_copy
|
|
197
|
+
end
|
|
198
|
+
|
|
204
199
|
def inspect # :nodoc:
|
|
205
200
|
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
|
|
206
201
|
end
|
|
@@ -32,13 +32,16 @@ module AbstractController
|
|
|
32
32
|
included do
|
|
33
33
|
extend ConfigMethods
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
singleton_class.delegate :default_static_extension, :default_static_extension=, to: :config
|
|
36
|
+
delegate :default_static_extension, :default_static_extension=, to: :config
|
|
36
37
|
self.default_static_extension ||= ".html"
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
singleton_class.delegate :perform_caching, :perform_caching=, to: :config
|
|
40
|
+
delegate :perform_caching, :perform_caching=, to: :config
|
|
39
41
|
self.perform_caching = true if perform_caching.nil?
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
singleton_class.delegate :enable_fragment_cache_logging, :enable_fragment_cache_logging=, to: :config
|
|
44
|
+
delegate :enable_fragment_cache_logging, :enable_fragment_cache_logging=, to: :config
|
|
42
45
|
self.enable_fragment_cache_logging = false
|
|
43
46
|
|
|
44
47
|
class_attribute :_view_cache_dependencies, default: []
|
|
@@ -29,6 +29,8 @@ module AbstractController
|
|
|
29
29
|
# ActiveSupport::Callbacks.
|
|
30
30
|
include ActiveSupport::Callbacks
|
|
31
31
|
|
|
32
|
+
DEFAULT_INTERNAL_METHODS = [:_run_process_action_callbacks].freeze # :nodoc:
|
|
33
|
+
|
|
32
34
|
included do
|
|
33
35
|
define_callbacks :process_action,
|
|
34
36
|
terminator: ->(controller, result_lambda) { result_lambda.call; controller.performed? },
|
|
@@ -251,6 +253,10 @@ module AbstractController
|
|
|
251
253
|
# *_action is the same as append_*_action
|
|
252
254
|
alias_method :"append_#{callback}_action", :"#{callback}_action"
|
|
253
255
|
end
|
|
256
|
+
|
|
257
|
+
def internal_methods # :nodoc:
|
|
258
|
+
super.concat(DEFAULT_INTERNAL_METHODS)
|
|
259
|
+
end
|
|
254
260
|
end
|
|
255
261
|
|
|
256
262
|
private
|
|
@@ -27,7 +27,7 @@ module AbstractController
|
|
|
27
27
|
def method_missing(symbol, ...)
|
|
28
28
|
unless mime_constant = Mime[symbol]
|
|
29
29
|
raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \
|
|
30
|
-
"https://guides.rubyonrails.org/
|
|
30
|
+
"https://guides.rubyonrails.org/action_controller_advanced_topics.html#restful-downloads. " \
|
|
31
31
|
"If you meant to respond to a variant like :tablet or :phone, not a custom format, " \
|
|
32
32
|
"be sure to nest your variant response within a format response: " \
|
|
33
33
|
"format.html { |html| html.tablet { ... } }"
|
|
@@ -90,7 +90,7 @@ module AbstractController
|
|
|
90
90
|
#--
|
|
91
91
|
# Implemented by Resolution#modules_for_helpers.
|
|
92
92
|
|
|
93
|
-
# :method:
|
|
93
|
+
# :method: all_helpers_from_path
|
|
94
94
|
# :call-seq: all_helpers_from_path(path)
|
|
95
95
|
#
|
|
96
96
|
# Returns a list of helper names in a given path.
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
require "action_view"
|
|
6
6
|
require "action_controller/log_subscriber"
|
|
7
|
+
require "action_controller/structured_event_subscriber"
|
|
7
8
|
require "action_controller/metal/params_wrapper"
|
|
8
9
|
|
|
9
10
|
module ActionController
|
|
@@ -128,7 +129,7 @@ module ActionController
|
|
|
128
129
|
#
|
|
129
130
|
# Action Controller sends content to the user by using one of five rendering
|
|
130
131
|
# methods. The most versatile and common is the rendering of a template.
|
|
131
|
-
#
|
|
132
|
+
# Also included with \Rails is Action View, which enables rendering of ERB
|
|
132
133
|
# templates. It's automatically configured. The controller passes objects to the
|
|
133
134
|
# view by assigning instance variables:
|
|
134
135
|
#
|
|
@@ -266,7 +267,7 @@ module ActionController
|
|
|
266
267
|
ParamsWrapper
|
|
267
268
|
]
|
|
268
269
|
|
|
269
|
-
# Note: Documenting these severely
|
|
270
|
+
# Note: Documenting these severely degrades the performance of rdoc
|
|
270
271
|
# :stopdoc:
|
|
271
272
|
include AbstractController::Rendering
|
|
272
273
|
include AbstractController::Translation
|
|
@@ -9,9 +9,8 @@ module ActionController
|
|
|
9
9
|
# of calculations, renderings, and database calls around for subsequent
|
|
10
10
|
# requests.
|
|
11
11
|
#
|
|
12
|
-
# You can read more about each approach by clicking the modules below.
|
|
13
|
-
#
|
|
14
12
|
# Note: To turn off all caching provided by Action Controller, set
|
|
13
|
+
#
|
|
15
14
|
# config.action_controller.perform_caching = false
|
|
16
15
|
#
|
|
17
16
|
# ## Caching stores
|
|
@@ -22,10 +22,10 @@ module ActionController
|
|
|
22
22
|
# default_form_builder AdminFormBuilder
|
|
23
23
|
# end
|
|
24
24
|
#
|
|
25
|
-
# Then in the view any form using `form_for` will be an
|
|
26
|
-
# specified form builder:
|
|
25
|
+
# Then in the view any form using `form_with` or `form_for` will be an
|
|
26
|
+
# instance of the specified form builder:
|
|
27
27
|
#
|
|
28
|
-
# <%=
|
|
28
|
+
# <%= form_with(model: @instance) do |builder| %>
|
|
29
29
|
# <%= builder.special_field(:name) %>
|
|
30
30
|
# <% end %>
|
|
31
31
|
module FormBuilder
|
|
@@ -40,7 +40,7 @@ module ActionController
|
|
|
40
40
|
# rendered by this controller and its subclasses.
|
|
41
41
|
#
|
|
42
42
|
# #### Parameters
|
|
43
|
-
# * `builder` - Default form builder
|
|
43
|
+
# * `builder` - Default form builder. Accepts a subclass of
|
|
44
44
|
# ActionView::Helpers::FormBuilder
|
|
45
45
|
def default_form_builder(builder)
|
|
46
46
|
self._default_form_builder = builder
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# :markup: markdown
|
|
4
|
-
|
|
5
3
|
module ActionController
|
|
6
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
4
|
+
class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
|
|
7
5
|
INTERNAL_PARAMS = %w(controller action format _method only_path)
|
|
8
6
|
|
|
7
|
+
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
|
8
|
+
|
|
9
9
|
def start_processing(event)
|
|
10
10
|
return unless logger.info?
|
|
11
11
|
|
|
@@ -49,6 +49,17 @@ module ActionController
|
|
|
49
49
|
end
|
|
50
50
|
subscribe_log_level :halted_callback, :info
|
|
51
51
|
|
|
52
|
+
# Manually subscribed below
|
|
53
|
+
def rescue_from_callback(event)
|
|
54
|
+
exception = event.payload[:exception]
|
|
55
|
+
|
|
56
|
+
exception_backtrace = exception.backtrace&.first
|
|
57
|
+
exception_backtrace = exception_backtrace&.delete_prefix("#{Rails.root}/") if defined?(Rails.root) && Rails.root
|
|
58
|
+
|
|
59
|
+
info { "rescue_from handled #{exception.class} (#{exception.message}) - #{exception_backtrace}" }
|
|
60
|
+
end
|
|
61
|
+
subscribe_log_level :rescue_from_callback, :info
|
|
62
|
+
|
|
52
63
|
def send_file(event)
|
|
53
64
|
info { "Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)" }
|
|
54
65
|
end
|
|
@@ -56,6 +67,10 @@ module ActionController
|
|
|
56
67
|
|
|
57
68
|
def redirect_to(event)
|
|
58
69
|
info { "Redirected to #{event.payload[:location]}" }
|
|
70
|
+
|
|
71
|
+
if ActionDispatch.verbose_redirect_logs && (source = redirect_source_location)
|
|
72
|
+
info { "↳ #{source}" }
|
|
73
|
+
end
|
|
59
74
|
end
|
|
60
75
|
subscribe_log_level :redirect_to, :info
|
|
61
76
|
|
|
@@ -90,6 +105,10 @@ module ActionController
|
|
|
90
105
|
def logger
|
|
91
106
|
ActionController::Base.logger
|
|
92
107
|
end
|
|
108
|
+
|
|
109
|
+
def redirect_source_location
|
|
110
|
+
backtrace_cleaner.first_clean_frame
|
|
111
|
+
end
|
|
93
112
|
end
|
|
94
113
|
end
|
|
95
114
|
|
|
@@ -14,7 +14,7 @@ module ActionController # :nodoc:
|
|
|
14
14
|
# aren't reporting a user-agent header, will be allowed access.
|
|
15
15
|
#
|
|
16
16
|
# A browser that's blocked will by default be served the file in
|
|
17
|
-
# public/406-unsupported-browser.html with
|
|
17
|
+
# public/406-unsupported-browser.html with an HTTP status code of "406 Not
|
|
18
18
|
# Acceptable".
|
|
19
19
|
#
|
|
20
20
|
# In addition to specifically named browser versions, you can also pass
|
|
@@ -36,6 +36,16 @@ module ActionController # :nodoc:
|
|
|
36
36
|
# end
|
|
37
37
|
#
|
|
38
38
|
# class ApplicationController < ActionController::Base
|
|
39
|
+
# # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has
|
|
40
|
+
# allow_browser versions: :modern, block: :handle_outdated_browser
|
|
41
|
+
#
|
|
42
|
+
# private
|
|
43
|
+
# def handle_outdated_browser
|
|
44
|
+
# render file: Rails.root.join("public/custom-error.html"), status: :not_acceptable
|
|
45
|
+
# end
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# class ApplicationController < ActionController::Base
|
|
39
49
|
# # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+.
|
|
40
50
|
# allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
|
|
41
51
|
# end
|
|
@@ -55,7 +65,7 @@ module ActionController # :nodoc:
|
|
|
55
65
|
|
|
56
66
|
if BrowserBlocker.new(request, versions: versions).blocked?
|
|
57
67
|
ActiveSupport::Notifications.instrument("browser_block.action_controller", request: request, versions: versions) do
|
|
58
|
-
instance_exec(&block)
|
|
68
|
+
block.is_a?(Symbol) ? send(block) : instance_exec(&block)
|
|
59
69
|
end
|
|
60
70
|
end
|
|
61
71
|
end
|
|
@@ -259,6 +259,9 @@ module ActionController
|
|
|
259
259
|
# `:stale_if_error`
|
|
260
260
|
# : Sets the value of the `stale-if-error` directive.
|
|
261
261
|
#
|
|
262
|
+
# `:immutable`
|
|
263
|
+
# : If true, adds the `immutable` directive.
|
|
264
|
+
#
|
|
262
265
|
#
|
|
263
266
|
# Any additional key-value pairs are concatenated as directives. For a list of
|
|
264
267
|
# supported `Cache-Control` directives, see the [article on
|
|
@@ -292,6 +295,7 @@ module ActionController
|
|
|
292
295
|
must_revalidate: options.delete(:must_revalidate),
|
|
293
296
|
stale_while_revalidate: options.delete(:stale_while_revalidate),
|
|
294
297
|
stale_if_error: options.delete(:stale_if_error),
|
|
298
|
+
immutable: options.delete(:immutable),
|
|
295
299
|
)
|
|
296
300
|
options.delete(:private)
|
|
297
301
|
|
|
@@ -315,7 +319,7 @@ module ActionController
|
|
|
315
319
|
# user's web browser. To allow proxies to cache the response, set `true` to
|
|
316
320
|
# indicate that they can serve the cached response to all users.
|
|
317
321
|
def http_cache_forever(public: false)
|
|
318
|
-
expires_in 100.years, public: public
|
|
322
|
+
expires_in 100.years, public: public, immutable: true
|
|
319
323
|
|
|
320
324
|
yield if stale?(etag: request.fullpath,
|
|
321
325
|
last_modified: Time.new(2011, 1, 1).utc,
|
|
@@ -328,6 +332,31 @@ module ActionController
|
|
|
328
332
|
response.cache_control.replace(no_store: true)
|
|
329
333
|
end
|
|
330
334
|
|
|
335
|
+
# Adds the `must-understand` directive to the `Cache-Control` header, which indicates
|
|
336
|
+
# that a cache MUST understand the semantics of the response status code that has been
|
|
337
|
+
# received, or discard the response.
|
|
338
|
+
#
|
|
339
|
+
# This is particularly useful when returning responses with new or uncommon
|
|
340
|
+
# status codes that might not be properly interpreted by older caches.
|
|
341
|
+
#
|
|
342
|
+
# #### Example
|
|
343
|
+
#
|
|
344
|
+
# def show
|
|
345
|
+
# @article = Article.find(params[:id])
|
|
346
|
+
#
|
|
347
|
+
# if @article.early_access?
|
|
348
|
+
# must_understand
|
|
349
|
+
# render status: 203 # Non-Authoritative Information
|
|
350
|
+
# else
|
|
351
|
+
# fresh_when @article
|
|
352
|
+
# end
|
|
353
|
+
# end
|
|
354
|
+
#
|
|
355
|
+
def must_understand
|
|
356
|
+
response.cache_control[:must_understand] = true
|
|
357
|
+
response.cache_control[:no_store] = true
|
|
358
|
+
end
|
|
359
|
+
|
|
331
360
|
private
|
|
332
361
|
def combine_etags(validator, options)
|
|
333
362
|
[validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact
|
|
@@ -28,7 +28,8 @@ module ActionController # :nodoc:
|
|
|
28
28
|
# `send_file(params[:path])` allows a malicious user to download any file on
|
|
29
29
|
# your server.
|
|
30
30
|
#
|
|
31
|
-
# Options:
|
|
31
|
+
# #### Options:
|
|
32
|
+
#
|
|
32
33
|
# * `:filename` - suggests a filename for the browser to use. Defaults to
|
|
33
34
|
# `File.basename(path)`.
|
|
34
35
|
# * `:type` - specifies an HTTP content type. You can specify either a string
|
|
@@ -90,7 +91,8 @@ module ActionController # :nodoc:
|
|
|
90
91
|
# inline data. You may also set the content type, the file name, and other
|
|
91
92
|
# things.
|
|
92
93
|
#
|
|
93
|
-
# Options:
|
|
94
|
+
# #### Options:
|
|
95
|
+
#
|
|
94
96
|
# * `:filename` - suggests a filename for the browser to use.
|
|
95
97
|
# * `:type` - specifies an HTTP content type. Defaults to
|
|
96
98
|
# `application/octet-stream`. You can specify either a string or a symbol
|
|
@@ -132,9 +134,7 @@ module ActionController # :nodoc:
|
|
|
132
134
|
raise ArgumentError, ":type option required" if content_type.nil?
|
|
133
135
|
|
|
134
136
|
if content_type.is_a?(Symbol)
|
|
135
|
-
|
|
136
|
-
raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension
|
|
137
|
-
self.content_type = extension
|
|
137
|
+
self.content_type = content_type
|
|
138
138
|
else
|
|
139
139
|
if !type_provided && options[:filename]
|
|
140
140
|
# If type wasn't provided, try guessing from file extension.
|
|
@@ -38,15 +38,12 @@ module ActionController # :nodoc:
|
|
|
38
38
|
define_method(type) do
|
|
39
39
|
request.flash[type]
|
|
40
40
|
end
|
|
41
|
+
private type
|
|
41
42
|
helper_method(type) if respond_to?(:helper_method)
|
|
42
43
|
|
|
43
44
|
self._flash_types += [type]
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
|
-
|
|
47
|
-
def action_methods # :nodoc:
|
|
48
|
-
@action_methods ||= super - _flash_types.map(&:to_s).to_set
|
|
49
|
-
end
|
|
50
47
|
end
|
|
51
48
|
|
|
52
49
|
private
|
|
@@ -25,6 +25,8 @@ module ActionController
|
|
|
25
25
|
raise ArgumentError, "#{status.inspect} is not a valid value for `status`."
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
raise ::AbstractController::DoubleRenderError if response_body
|
|
29
|
+
|
|
28
30
|
status ||= :ok
|
|
29
31
|
|
|
30
32
|
if options
|
|
@@ -41,7 +43,7 @@ module ActionController
|
|
|
41
43
|
|
|
42
44
|
if include_content?(response_code)
|
|
43
45
|
unless self.media_type
|
|
44
|
-
self.content_type = content_type || ((f = formats) && Mime[f.first]) ||
|
|
46
|
+
self.content_type = content_type || ((f = formats) && Mime[f.first]) || :html
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
response.charset = false
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# :markup: markdown
|
|
4
4
|
|
|
5
|
-
require "benchmark"
|
|
6
5
|
require "abstract_controller/logger"
|
|
7
6
|
|
|
8
7
|
module ActionController
|
|
@@ -29,7 +28,7 @@ module ActionController
|
|
|
29
28
|
def render(*)
|
|
30
29
|
render_output = nil
|
|
31
30
|
self.view_runtime = cleanup_view_runtime do
|
|
32
|
-
Benchmark.
|
|
31
|
+
ActiveSupport::Benchmark.realtime(:float_millisecond) { render_output = super }
|
|
33
32
|
end
|
|
34
33
|
render_output
|
|
35
34
|
end
|
|
@@ -53,12 +53,53 @@ module ActionController
|
|
|
53
53
|
# response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x
|
|
54
54
|
# ...
|
|
55
55
|
# end
|
|
56
|
+
#
|
|
57
|
+
# ## Streaming and Execution State
|
|
58
|
+
#
|
|
59
|
+
# When streaming, the action is executed in a separate thread. By default, this thread
|
|
60
|
+
# shares execution state from the parent thread.
|
|
61
|
+
#
|
|
62
|
+
# You can configure which execution state keys should be excluded from being shared
|
|
63
|
+
# using the `config.action_controller.live_streaming_excluded_keys` configuration:
|
|
64
|
+
#
|
|
65
|
+
# # config/application.rb
|
|
66
|
+
# config.action_controller.live_streaming_excluded_keys = [:active_record_connected_to_stack]
|
|
67
|
+
#
|
|
68
|
+
# This is useful when using ActionController::Live inside a `connected_to` block. For example,
|
|
69
|
+
# if the parent request is reading from a replica using `connected_to(role: :reading)`, you may
|
|
70
|
+
# want the streaming thread to use its own connection context instead of inheriting the read-only
|
|
71
|
+
# context:
|
|
72
|
+
#
|
|
73
|
+
# # Without configuration, streaming thread inherits read-only connection
|
|
74
|
+
# ActiveRecord::Base.connected_to(role: :reading) do
|
|
75
|
+
# @posts = Post.all
|
|
76
|
+
# render stream: true # Streaming thread cannot write to database
|
|
77
|
+
# end
|
|
78
|
+
#
|
|
79
|
+
# # With configuration, streaming thread gets fresh connection context
|
|
80
|
+
# # config.action_controller.live_streaming_excluded_keys = [:active_record_connected_to_stack]
|
|
81
|
+
# ActiveRecord::Base.connected_to(role: :reading) do
|
|
82
|
+
# @posts = Post.all
|
|
83
|
+
# render stream: true # Streaming thread can write to database if needed
|
|
84
|
+
# end
|
|
85
|
+
#
|
|
86
|
+
# Common keys you might want to exclude:
|
|
87
|
+
# - `:active_record_connected_to_stack` - Database connection routing and roles
|
|
88
|
+
# - `:active_record_prohibit_shard_swapping` - Shard swapping restrictions
|
|
89
|
+
#
|
|
90
|
+
# By default, no keys are excluded to maintain backward compatibility.
|
|
56
91
|
module Live
|
|
57
92
|
extend ActiveSupport::Concern
|
|
58
93
|
|
|
94
|
+
mattr_accessor :live_streaming_excluded_keys, default: []
|
|
95
|
+
|
|
96
|
+
included do
|
|
97
|
+
class_attribute :live_streaming_excluded_keys, instance_accessor: false, default: Live.live_streaming_excluded_keys
|
|
98
|
+
end
|
|
99
|
+
|
|
59
100
|
module ClassMethods
|
|
60
101
|
def make_response!(request)
|
|
61
|
-
if request.get_header("HTTP_VERSION") == "HTTP/1.0"
|
|
102
|
+
if (request.get_header("SERVER_PROTOCOL") || request.get_header("HTTP_VERSION")) == "HTTP/1.0"
|
|
62
103
|
super
|
|
63
104
|
else
|
|
64
105
|
Live::Response.new.tap do |res|
|
|
@@ -133,15 +174,16 @@ module ActionController
|
|
|
133
174
|
private
|
|
134
175
|
def perform_write(json, options)
|
|
135
176
|
current_options = @options.merge(options).stringify_keys
|
|
136
|
-
|
|
177
|
+
event = +""
|
|
137
178
|
PERMITTED_OPTIONS.each do |option_name|
|
|
138
179
|
if (option_value = current_options[option_name])
|
|
139
|
-
|
|
180
|
+
event << "#{option_name}: #{option_value}\n"
|
|
140
181
|
end
|
|
141
182
|
end
|
|
142
183
|
|
|
143
184
|
message = json.gsub("\n", "\ndata: ")
|
|
144
|
-
|
|
185
|
+
event << "data: #{message}\n\n"
|
|
186
|
+
@stream.write event
|
|
145
187
|
end
|
|
146
188
|
end
|
|
147
189
|
|
|
@@ -171,12 +213,6 @@ module ActionController
|
|
|
171
213
|
@ignore_disconnect = false
|
|
172
214
|
end
|
|
173
215
|
|
|
174
|
-
# ActionDispatch::Response delegates #to_ary to the internal
|
|
175
|
-
# ActionDispatch::Response::Buffer, defining #to_ary is an indicator that the
|
|
176
|
-
# response body can be buffered and/or cached by Rack middlewares, this is not
|
|
177
|
-
# the case for Live responses so we undefine it for this Buffer subclass.
|
|
178
|
-
undef_method :to_ary
|
|
179
|
-
|
|
180
216
|
def write(string)
|
|
181
217
|
unless @response.committed?
|
|
182
218
|
@response.headers["Cache-Control"] ||= "no-cache"
|
|
@@ -242,12 +278,7 @@ module ActionController
|
|
|
242
278
|
|
|
243
279
|
private
|
|
244
280
|
def each_chunk(&block)
|
|
245
|
-
|
|
246
|
-
str = nil
|
|
247
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
248
|
-
str = @buf.pop
|
|
249
|
-
end
|
|
250
|
-
break unless str
|
|
281
|
+
while str = @buf.pop
|
|
251
282
|
yield str
|
|
252
283
|
end
|
|
253
284
|
end
|
|
@@ -281,16 +312,15 @@ module ActionController
|
|
|
281
312
|
# This processes the action in a child thread. It lets us return the response
|
|
282
313
|
# code and headers back up the Rack stack, and still process the body in
|
|
283
314
|
# parallel with sending data to the client.
|
|
284
|
-
new_controller_thread
|
|
315
|
+
new_controller_thread do
|
|
285
316
|
ActiveSupport::Dependencies.interlock.running do
|
|
286
317
|
t2 = Thread.current
|
|
287
318
|
|
|
288
319
|
# Since we're processing the view in a different thread, copy the thread locals
|
|
289
320
|
# from the main thread to the child thread. :'(
|
|
290
321
|
locals.each { |k, v| t2[k] = v }
|
|
291
|
-
ActiveSupport::IsolatedExecutionState.share_with(t1)
|
|
292
322
|
|
|
293
|
-
|
|
323
|
+
ActiveSupport::IsolatedExecutionState.share_with(t1, except: self.class.live_streaming_excluded_keys) do
|
|
294
324
|
super(name)
|
|
295
325
|
rescue => e
|
|
296
326
|
if @_response.committed?
|
|
@@ -307,15 +337,15 @@ module ActionController
|
|
|
307
337
|
error = e
|
|
308
338
|
end
|
|
309
339
|
ensure
|
|
340
|
+
clean_up_thread_locals(locals, t2)
|
|
341
|
+
|
|
310
342
|
@_response.commit!
|
|
311
343
|
end
|
|
312
344
|
end
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
316
|
-
@_response.await_commit
|
|
317
345
|
end
|
|
318
346
|
|
|
347
|
+
@_response.await_commit
|
|
348
|
+
|
|
319
349
|
raise error if error
|
|
320
350
|
end
|
|
321
351
|
|
|
@@ -328,7 +358,8 @@ module ActionController
|
|
|
328
358
|
# or other running data where you don't want the entire file buffered in memory
|
|
329
359
|
# first. Similar to send_data, but where the data is generated live.
|
|
330
360
|
#
|
|
331
|
-
# Options:
|
|
361
|
+
# #### Options:
|
|
362
|
+
#
|
|
332
363
|
# * `:filename` - suggests a filename for the browser to use.
|
|
333
364
|
# * `:type` - specifies an HTTP content type. You can specify either a string
|
|
334
365
|
# or a symbol for a registered type with `Mime::Type.register`, for example
|
|
@@ -371,11 +402,20 @@ module ActionController
|
|
|
371
402
|
# data from the response bodies. Nobody should call this method except in Rails
|
|
372
403
|
# internals. Seriously!
|
|
373
404
|
def new_controller_thread # :nodoc:
|
|
374
|
-
|
|
405
|
+
ActionController::Live.live_thread_pool_executor.post do
|
|
375
406
|
t2 = Thread.current
|
|
376
407
|
t2.abort_on_exception = true
|
|
377
408
|
yield
|
|
378
|
-
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Ensure we clean up any thread locals we copied so that the thread can reused.
|
|
413
|
+
def clean_up_thread_locals(locals, thread) # :nodoc:
|
|
414
|
+
locals.each { |k, _| thread[k] = nil }
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def self.live_thread_pool_executor
|
|
418
|
+
@live_thread_pool_executor ||= Concurrent::CachedThreadPool.new(name: "action_controller.live")
|
|
379
419
|
end
|
|
380
420
|
|
|
381
421
|
def log_error(exception)
|
|
@@ -198,14 +198,14 @@ module ActionController
|
|
|
198
198
|
# # enables the parameter wrapper for XML format
|
|
199
199
|
#
|
|
200
200
|
# wrap_parameters :person
|
|
201
|
-
# # wraps parameters into
|
|
201
|
+
# # wraps parameters into params[:person] hash
|
|
202
202
|
#
|
|
203
203
|
# wrap_parameters Person
|
|
204
204
|
# # wraps parameters by determining the wrapper key from Person class
|
|
205
|
-
# # (
|
|
205
|
+
# # (:person, in this case) and the list of attribute names
|
|
206
206
|
#
|
|
207
207
|
# wrap_parameters include: [:username, :title]
|
|
208
|
-
# # wraps only
|
|
208
|
+
# # wraps only :username and :title attributes from parameters.
|
|
209
209
|
#
|
|
210
210
|
# wrap_parameters false
|
|
211
211
|
# # disables parameters wrapping for this controller altogether.
|