actionpack 7.0.8.7 → 7.2.2.1

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.
Files changed (171) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -537
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +119 -106
  7. data/lib/abstract_controller/caching/fragments.rb +51 -52
  8. data/lib/abstract_controller/caching.rb +2 -0
  9. data/lib/abstract_controller/callbacks.rb +94 -67
  10. data/lib/abstract_controller/collector.rb +6 -6
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +2 -0
  13. data/lib/abstract_controller/helpers.rb +121 -91
  14. data/lib/abstract_controller/logger.rb +2 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +14 -13
  17. data/lib/abstract_controller/translation.rb +12 -30
  18. data/lib/abstract_controller/url_for.rb +9 -5
  19. data/lib/abstract_controller.rb +8 -0
  20. data/lib/action_controller/api/api_rendering.rb +2 -0
  21. data/lib/action_controller/api.rb +78 -73
  22. data/lib/action_controller/base.rb +199 -141
  23. data/lib/action_controller/caching.rb +16 -11
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +21 -16
  26. data/lib/action_controller/log_subscriber.rb +19 -5
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  29. data/lib/action_controller/metal/conditional_get.rb +187 -174
  30. data/lib/action_controller/metal/content_security_policy.rb +26 -25
  31. data/lib/action_controller/metal/cookies.rb +4 -2
  32. data/lib/action_controller/metal/data_streaming.rb +65 -54
  33. data/lib/action_controller/metal/default_headers.rb +6 -2
  34. data/lib/action_controller/metal/etag_with_flash.rb +4 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +18 -14
  36. data/lib/action_controller/metal/exceptions.rb +19 -9
  37. data/lib/action_controller/metal/flash.rb +12 -10
  38. data/lib/action_controller/metal/head.rb +20 -16
  39. data/lib/action_controller/metal/helpers.rb +64 -67
  40. data/lib/action_controller/metal/http_authentication.rb +212 -199
  41. data/lib/action_controller/metal/implicit_render.rb +21 -17
  42. data/lib/action_controller/metal/instrumentation.rb +22 -12
  43. data/lib/action_controller/metal/live.rb +125 -92
  44. data/lib/action_controller/metal/logging.rb +6 -4
  45. data/lib/action_controller/metal/mime_responds.rb +151 -142
  46. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  47. data/lib/action_controller/metal/params_wrapper.rb +58 -58
  48. data/lib/action_controller/metal/permissions_policy.rb +14 -13
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +110 -84
  51. data/lib/action_controller/metal/renderers.rb +50 -49
  52. data/lib/action_controller/metal/rendering.rb +103 -82
  53. data/lib/action_controller/metal/request_forgery_protection.rb +279 -161
  54. data/lib/action_controller/metal/rescue.rb +12 -8
  55. data/lib/action_controller/metal/streaming.rb +174 -132
  56. data/lib/action_controller/metal/strong_parameters.rb +598 -473
  57. data/lib/action_controller/metal/testing.rb +2 -0
  58. data/lib/action_controller/metal/url_for.rb +23 -14
  59. data/lib/action_controller/metal.rb +145 -61
  60. data/lib/action_controller/railtie.rb +25 -9
  61. data/lib/action_controller/railties/helpers.rb +2 -0
  62. data/lib/action_controller/renderer.rb +105 -66
  63. data/lib/action_controller/template_assertions.rb +4 -2
  64. data/lib/action_controller/test_case.rb +157 -128
  65. data/lib/action_controller.rb +17 -3
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +28 -29
  69. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +48 -45
  71. data/lib/action_dispatch/http/filter_parameters.rb +18 -8
  72. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  73. data/lib/action_dispatch/http/headers.rb +23 -21
  74. data/lib/action_dispatch/http/mime_negotiation.rb +37 -48
  75. data/lib/action_dispatch/http/mime_type.rb +60 -30
  76. data/lib/action_dispatch/http/mime_types.rb +5 -1
  77. data/lib/action_dispatch/http/parameters.rb +12 -10
  78. data/lib/action_dispatch/http/permissions_policy.rb +32 -27
  79. data/lib/action_dispatch/http/rack_cache.rb +4 -0
  80. data/lib/action_dispatch/http/request.rb +132 -79
  81. data/lib/action_dispatch/http/response.rb +136 -103
  82. data/lib/action_dispatch/http/upload.rb +19 -15
  83. data/lib/action_dispatch/http/url.rb +75 -73
  84. data/lib/action_dispatch/journey/formatter.rb +19 -6
  85. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +10 -8
  88. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +6 -5
  90. data/lib/action_dispatch/journey/parser.rb +4 -3
  91. data/lib/action_dispatch/journey/parser_extras.rb +2 -0
  92. data/lib/action_dispatch/journey/path/pattern.rb +18 -15
  93. data/lib/action_dispatch/journey/route.rb +12 -9
  94. data/lib/action_dispatch/journey/router/utils.rb +16 -15
  95. data/lib/action_dispatch/journey/router.rb +13 -10
  96. data/lib/action_dispatch/journey/routes.rb +6 -4
  97. data/lib/action_dispatch/journey/scanner.rb +4 -2
  98. data/lib/action_dispatch/journey/visitors.rb +2 -0
  99. data/lib/action_dispatch/journey.rb +2 -0
  100. data/lib/action_dispatch/log_subscriber.rb +25 -0
  101. data/lib/action_dispatch/middleware/actionable_exceptions.rb +7 -6
  102. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  103. data/lib/action_dispatch/middleware/callbacks.rb +4 -0
  104. data/lib/action_dispatch/middleware/cookies.rb +192 -194
  105. data/lib/action_dispatch/middleware/debug_exceptions.rb +36 -27
  106. data/lib/action_dispatch/middleware/debug_locks.rb +18 -13
  107. data/lib/action_dispatch/middleware/debug_view.rb +9 -2
  108. data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -27
  109. data/lib/action_dispatch/middleware/executor.rb +9 -1
  110. data/lib/action_dispatch/middleware/flash.rb +65 -46
  111. data/lib/action_dispatch/middleware/host_authorization.rb +22 -17
  112. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -8
  113. data/lib/action_dispatch/middleware/reloader.rb +9 -5
  114. data/lib/action_dispatch/middleware/remote_ip.rb +88 -83
  115. data/lib/action_dispatch/middleware/request_id.rb +15 -8
  116. data/lib/action_dispatch/middleware/server_timing.rb +8 -6
  117. data/lib/action_dispatch/middleware/session/abstract_store.rb +7 -0
  118. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -7
  119. data/lib/action_dispatch/middleware/session/cookie_store.rb +32 -25
  120. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +9 -3
  121. data/lib/action_dispatch/middleware/show_exceptions.rb +42 -28
  122. data/lib/action_dispatch/middleware/ssl.rb +60 -45
  123. data/lib/action_dispatch/middleware/stack.rb +15 -9
  124. data/lib/action_dispatch/middleware/static.rb +40 -34
  125. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  126. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  127. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  129. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  130. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  134. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  137. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  138. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +47 -38
  139. data/lib/action_dispatch/railtie.rb +12 -4
  140. data/lib/action_dispatch/request/session.rb +39 -27
  141. data/lib/action_dispatch/request/utils.rb +10 -3
  142. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  143. data/lib/action_dispatch/routing/inspector.rb +59 -9
  144. data/lib/action_dispatch/routing/mapper.rb +686 -639
  145. data/lib/action_dispatch/routing/polymorphic_routes.rb +70 -61
  146. data/lib/action_dispatch/routing/redirection.rb +52 -38
  147. data/lib/action_dispatch/routing/route_set.rb +106 -62
  148. data/lib/action_dispatch/routing/routes_proxy.rb +16 -19
  149. data/lib/action_dispatch/routing/url_for.rb +131 -122
  150. data/lib/action_dispatch/routing.rb +152 -150
  151. data/lib/action_dispatch/system_test_case.rb +91 -81
  152. data/lib/action_dispatch/system_testing/browser.rb +27 -19
  153. data/lib/action_dispatch/system_testing/driver.rb +16 -22
  154. data/lib/action_dispatch/system_testing/server.rb +2 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +53 -31
  156. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +9 -7
  158. data/lib/action_dispatch/testing/assertions/response.rb +36 -26
  159. data/lib/action_dispatch/testing/assertions/routing.rb +203 -95
  160. data/lib/action_dispatch/testing/assertions.rb +5 -1
  161. data/lib/action_dispatch/testing/integration.rb +240 -229
  162. data/lib/action_dispatch/testing/request_encoder.rb +6 -1
  163. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  164. data/lib/action_dispatch/testing/test_process.rb +14 -9
  165. data/lib/action_dispatch/testing/test_request.rb +4 -2
  166. data/lib/action_dispatch/testing/test_response.rb +34 -19
  167. data/lib/action_dispatch.rb +52 -21
  168. data/lib/action_pack/gem_version.rb +6 -4
  169. data/lib/action_pack/version.rb +3 -1
  170. data/lib/action_pack.rb +18 -17
  171. metadata +86 -27
@@ -1,31 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionController
4
- # Handles implicit rendering for a controller action that does not
5
- # explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
6
+ # # Action Controller Implicit Render
7
+ #
8
+ # Handles implicit rendering for a controller action that does not explicitly
9
+ # respond with `render`, `respond_to`, `redirect`, or `head`.
6
10
  #
7
- # For API controllers, the implicit response is always <tt>204 No Content</tt>.
11
+ # For API controllers, the implicit response is always `204 No Content`.
8
12
  #
9
- # For all other controllers, we use these heuristics to decide whether to
10
- # render a template, raise an error for a missing template, or respond with
11
- # <tt>204 No Content</tt>:
13
+ # For all other controllers, we use these heuristics to decide whether to render
14
+ # a template, raise an error for a missing template, or respond with `204 No
15
+ # Content`:
12
16
  #
13
- # First, if we DO find a template, it's rendered. Template lookup accounts
14
- # for the action name, locales, format, variant, template handlers, and more
15
- # (see +render+ for details).
17
+ # First, if we DO find a template, it's rendered. Template lookup accounts for
18
+ # the action name, locales, format, variant, template handlers, and more (see
19
+ # `render` for details).
16
20
  #
17
21
  # Second, if we DON'T find a template but the controller action does have
18
- # templates for other formats, variants, etc., then we trust that you meant
19
- # to provide a template for this response, too, and we raise
20
- # <tt>ActionController::UnknownFormat</tt> with an explanation.
22
+ # templates for other formats, variants, etc., then we trust that you meant to
23
+ # provide a template for this response, too, and we raise
24
+ # ActionController::UnknownFormat with an explanation.
21
25
  #
22
26
  # Third, if we DON'T find a template AND the request is a page load in a web
23
- # browser (technically, a non-XHR GET request for an HTML response) where
24
- # you reasonably expect to have rendered a template, then we raise
25
- # <tt>ActionController::MissingExactTemplate</tt> with an explanation.
27
+ # browser (technically, a non-XHR GET request for an HTML response) where you
28
+ # reasonably expect to have rendered a template, then we raise
29
+ # ActionController::MissingExactTemplate with an explanation.
26
30
  #
27
31
  # Finally, if we DON'T find a template AND the request isn't a browser page
28
- # load, then we implicitly respond with <tt>204 No Content</tt>.
32
+ # load, then we implicitly respond with `204 No Content`.
29
33
  module ImplicitRender
30
34
  # :stopdoc:
31
35
  include BasicImplicitRender
@@ -42,7 +46,7 @@ module ActionController
42
46
  raise ActionController::UnknownFormat, message
43
47
  elsif interactive_browser_request?
44
48
  message = "#{self.class.name}\##{action_name} is missing a template for request formats: #{request.formats.map(&:to_s).join(',')}"
45
- raise ActionController::MissingExactTemplate, message
49
+ raise ActionController::MissingExactTemplate.new(message, self.class, action_name)
46
50
  else
47
51
  logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
48
52
  super
@@ -1,12 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "benchmark"
4
6
  require "abstract_controller/logger"
5
7
 
6
8
  module ActionController
7
- # Adds instrumentation to several ends in ActionController::Base. It also provides
8
- # some hooks related with process_action. This allows an ORM like Active Record
9
- # and/or DataMapper to plug in ActionController and show related information.
9
+ # # Action Controller Instrumentation
10
+ #
11
+ # Adds instrumentation to several ends in ActionController::Base. It also
12
+ # provides some hooks related with process_action. This allows an ORM like
13
+ # Active Record and/or DataMapper to plug in ActionController and show related
14
+ # information.
10
15
  #
11
16
  # Check ActiveRecord::Railties::ControllerRuntime for an example.
12
17
  module Instrumentation
@@ -16,6 +21,11 @@ module ActionController
16
21
 
17
22
  attr_internal :view_runtime
18
23
 
24
+ def initialize(...) # :nodoc:
25
+ super
26
+ self.view_runtime = nil
27
+ end
28
+
19
29
  def render(*)
20
30
  render_output = nil
21
31
  self.view_runtime = cleanup_view_runtime do
@@ -58,7 +68,7 @@ module ActionController
58
68
  headers: request.headers,
59
69
  format: request.format.ref,
60
70
  method: request.request_method,
61
- path: request.fullpath
71
+ path: request.filtered_path
62
72
  }
63
73
 
64
74
  ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
@@ -84,23 +94,23 @@ module ActionController
84
94
  # A hook which allows you to clean up any time, wrongly taken into account in
85
95
  # views, like database querying time.
86
96
  #
87
- # def cleanup_view_runtime
88
- # super - time_taken_in_something_expensive
89
- # end
97
+ # def cleanup_view_runtime
98
+ # super - time_taken_in_something_expensive
99
+ # end
90
100
  def cleanup_view_runtime # :doc:
91
101
  yield
92
102
  end
93
103
 
94
- # Every time after an action is processed, this method is invoked
95
- # with the payload, so you can add more information.
104
+ # Every time after an action is processed, this method is invoked with the
105
+ # payload, so you can add more information.
96
106
  def append_info_to_payload(payload) # :doc:
97
107
  payload[:view_runtime] = view_runtime
98
108
  end
99
109
 
100
110
  module ClassMethods
101
- # A hook which allows other frameworks to log what happened during
102
- # controller process action. This method should return an array
103
- # with the messages to be added.
111
+ # A hook which allows other frameworks to log what happened during controller
112
+ # process action. This method should return an array with the messages to be
113
+ # added.
104
114
  def log_process_action(payload) # :nodoc:
105
115
  messages, view_runtime = [], payload[:view_runtime]
106
116
  messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
@@ -1,39 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "action_dispatch/http/response"
4
6
  require "delegate"
5
7
  require "active_support/json"
6
8
 
7
9
  module ActionController
8
- # Mix this module into your controller, and all actions in that controller
9
- # will be able to stream data to the client as it's written.
10
+ # # Action Controller Live
10
11
  #
11
- # class MyController < ActionController::Base
12
- # include ActionController::Live
12
+ # Mix this module into your controller, and all actions in that controller will
13
+ # be able to stream data to the client as it's written.
13
14
  #
14
- # def stream
15
- # response.headers['Content-Type'] = 'text/event-stream'
16
- # 100.times {
17
- # response.stream.write "hello world\n"
18
- # sleep 1
19
- # }
20
- # ensure
21
- # response.stream.close
15
+ # class MyController < ActionController::Base
16
+ # include ActionController::Live
17
+ #
18
+ # def stream
19
+ # response.headers['Content-Type'] = 'text/event-stream'
20
+ # 100.times {
21
+ # response.stream.write "hello world\n"
22
+ # sleep 1
23
+ # }
24
+ # ensure
25
+ # response.stream.close
26
+ # end
22
27
  # end
23
- # end
24
28
  #
25
- # There are a few caveats with this module. You *cannot* write headers after the
26
- # response has been committed (Response#committed? will return truthy).
27
- # Calling +write+ or +close+ on the response stream will cause the response
28
- # object to be committed. Make sure all headers are set before calling write
29
- # or close on your stream.
29
+ # There are a few caveats with this module. You **cannot** write headers after
30
+ # the response has been committed (Response#committed? will return truthy).
31
+ # Calling `write` or `close` on the response stream will cause the response
32
+ # object to be committed. Make sure all headers are set before calling write or
33
+ # close on your stream.
30
34
  #
31
- # You *must* call close on your stream when you're finished, otherwise the
35
+ # You **must** call close on your stream when you're finished, otherwise the
32
36
  # socket may be left open forever.
33
37
  #
34
38
  # The final caveat is that your actions are executed in a separate thread than
35
- # the main thread. Make sure your actions are thread safe, and this shouldn't
36
- # be a problem (don't share state across threads, etc).
39
+ # the main thread. Make sure your actions are thread safe, and this shouldn't be
40
+ # a problem (don't share state across threads, etc).
41
+ #
42
+ # Note that Rails includes `Rack::ETag` by default, which will buffer your
43
+ # response. As a result, streaming responses may not work properly with Rack
44
+ # 2.2.x, and you may need to implement workarounds in your application. You can
45
+ # either set the `ETag` or `Last-Modified` response headers or remove
46
+ # `Rack::ETag` from the middleware stack to address this issue.
47
+ #
48
+ # Here's an example of how you can set the `Last-Modified` header if your Rack
49
+ # version is 2.2.x:
50
+ #
51
+ # def stream
52
+ # response.headers["Content-Type"] = "text/event-stream"
53
+ # response.headers["Last-Modified"] = Time.now.httpdate # Add this line if your Rack version is 2.2.x
54
+ # ...
55
+ # end
37
56
  module Live
38
57
  extend ActiveSupport::Concern
39
58
 
@@ -49,42 +68,47 @@ module ActionController
49
68
  end
50
69
  end
51
70
 
52
- # This class provides the ability to write an SSE (Server Sent Event)
53
- # to an IO stream. The class is initialized with a stream and can be used
54
- # to either write a JSON string or an object which can be converted to JSON.
71
+ # # Action Controller Live Server Sent Events
72
+ #
73
+ # This class provides the ability to write an SSE (Server Sent Event) to an IO
74
+ # stream. The class is initialized with a stream and can be used to either write
75
+ # a JSON string or an object which can be converted to JSON.
55
76
  #
56
77
  # Writing an object will convert it into standard SSE format with whatever
57
78
  # options you have configured. You may choose to set the following options:
58
79
  #
59
- # 1) Event. If specified, an event with this name will be dispatched on
60
- # the browser.
61
- # 2) Retry. The reconnection time in milliseconds used when attempting
62
- # to send the event.
63
- # 3) Id. If the connection dies while sending an SSE to the browser, then
64
- # the server will receive a +Last-Event-ID+ header with value equal to +id+.
80
+ # `:event`
81
+ # : If specified, an event with this name will be dispatched on the browser.
82
+ #
83
+ # `:retry`
84
+ # : The reconnection time in milliseconds used when attempting to send the event.
65
85
  #
66
- # After setting an option in the constructor of the SSE object, all future
67
- # SSEs sent across the stream will use those options unless overridden.
86
+ # `:id`
87
+ # : If the connection dies while sending an SSE to the browser, then the
88
+ # server will receive a `Last-Event-ID` header with value equal to `id`.
89
+ #
90
+ # After setting an option in the constructor of the SSE object, all future SSEs
91
+ # sent across the stream will use those options unless overridden.
68
92
  #
69
93
  # Example Usage:
70
94
  #
71
- # class MyController < ActionController::Base
72
- # include ActionController::Live
95
+ # class MyController < ActionController::Base
96
+ # include ActionController::Live
73
97
  #
74
- # def index
75
- # response.headers['Content-Type'] = 'text/event-stream'
76
- # sse = SSE.new(response.stream, retry: 300, event: "event-name")
77
- # sse.write({ name: 'John'})
78
- # sse.write({ name: 'John'}, id: 10)
79
- # sse.write({ name: 'John'}, id: 10, event: "other-event")
80
- # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
81
- # ensure
82
- # sse.close
98
+ # def index
99
+ # response.headers['Content-Type'] = 'text/event-stream'
100
+ # sse = SSE.new(response.stream, retry: 300, event: "event-name")
101
+ # sse.write({ name: 'John'})
102
+ # sse.write({ name: 'John'}, id: 10)
103
+ # sse.write({ name: 'John'}, id: 10, event: "other-event")
104
+ # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
105
+ # ensure
106
+ # sse.close
107
+ # end
83
108
  # end
84
- # end
85
109
  #
86
- # Note: SSEs are not currently supported by IE. However, they are supported
87
- # by Chrome, Firefox, Opera, and Safari.
110
+ # Note: SSEs are not currently supported by IE. However, they are supported by
111
+ # Chrome, Firefox, Opera, and Safari.
88
112
  class SSE
89
113
  PERMITTED_OPTIONS = %w( retry event id )
90
114
 
@@ -134,10 +158,9 @@ module ActionController
134
158
 
135
159
  # Ignore that the client has disconnected.
136
160
  #
137
- # If this value is `true`, calling `write` after the client
138
- # disconnects will result in the written content being silently
139
- # discarded. If this value is `false` (the default), a
140
- # ClientDisconnected exception will be raised.
161
+ # If this value is `true`, calling `write` after the client disconnects will
162
+ # result in the written content being silently discarded. If this value is
163
+ # `false` (the default), a ClientDisconnected exception will be raised.
141
164
  attr_accessor :ignore_disconnect
142
165
 
143
166
  def initialize(response)
@@ -148,6 +171,12 @@ module ActionController
148
171
  @ignore_disconnect = false
149
172
  end
150
173
 
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
+
151
180
  def write(string)
152
181
  unless @response.committed?
153
182
  @response.headers["Cache-Control"] ||= "no-cache"
@@ -160,21 +189,20 @@ module ActionController
160
189
  @buf.clear
161
190
 
162
191
  unless @ignore_disconnect
163
- # Raise ClientDisconnected, which is a RuntimeError (not an
164
- # IOError), because that's more appropriate for something beyond
165
- # the developer's control.
192
+ # Raise ClientDisconnected, which is a RuntimeError (not an IOError), because
193
+ # that's more appropriate for something beyond the developer's control.
166
194
  raise ClientDisconnected, "client disconnected"
167
195
  end
168
196
  end
169
197
  end
170
198
 
171
- # Same as +write+ but automatically include a newline at the end of the string.
199
+ # Same as `write` but automatically include a newline at the end of the string.
172
200
  def writeln(string)
173
201
  write string.end_with?("\n") ? string : "#{string}\n"
174
202
  end
175
203
 
176
- # Write a 'close' event to the buffer; the producer/writing thread
177
- # uses this to notify us that it's finished supplying content.
204
+ # Write a 'close' event to the buffer; the producer/writing thread uses this to
205
+ # notify us that it's finished supplying content.
178
206
  #
179
207
  # See also #abort.
180
208
  def close
@@ -185,9 +213,8 @@ module ActionController
185
213
  end
186
214
  end
187
215
 
188
- # Inform the producer/writing thread that the client has
189
- # disconnected; the reading thread is no longer interested in
190
- # anything that's being written.
216
+ # Inform the producer/writing thread that the client has disconnected; the
217
+ # reading thread is no longer interested in anything that's being written.
191
218
  #
192
219
  # See also #close.
193
220
  def abort
@@ -199,8 +226,8 @@ module ActionController
199
226
 
200
227
  # Is the client still connected and waiting for content?
201
228
  #
202
- # The result of calling `write` when this is `false` is determined
203
- # by `ignore_disconnect`.
229
+ # The result of calling `write` when this is `false` is determined by
230
+ # `ignore_disconnect`.
204
231
  def connected?
205
232
  !@aborted
206
233
  end
@@ -251,15 +278,15 @@ module ActionController
251
278
  locals = t1.keys.map { |key| [key, t1[key]] }
252
279
 
253
280
  error = nil
254
- # This processes the action in a child thread. It lets us return the
255
- # response code and headers back up the Rack stack, and still process
256
- # the body in parallel with sending data to the client.
281
+ # This processes the action in a child thread. It lets us return the response
282
+ # code and headers back up the Rack stack, and still process the body in
283
+ # parallel with sending data to the client.
257
284
  new_controller_thread {
258
285
  ActiveSupport::Dependencies.interlock.running do
259
286
  t2 = Thread.current
260
287
 
261
- # Since we're processing the view in a different thread, copy the
262
- # thread locals from the main thread to the child thread. :'(
288
+ # Since we're processing the view in a different thread, copy the thread locals
289
+ # from the main thread to the child thread. :'(
263
290
  locals.each { |k, v| t2[k] = v }
264
291
  ActiveSupport::IsolatedExecutionState.share_with(t1)
265
292
 
@@ -297,46 +324,52 @@ module ActionController
297
324
  response.close if response
298
325
  end
299
326
 
300
- # Sends a stream to the browser, which is helpful when you're generating exports or other running data where you
301
- # don't want the entire file buffered in memory first. Similar to send_data, but where the data is generated live.
327
+ # Sends a stream to the browser, which is helpful when you're generating exports
328
+ # or other running data where you don't want the entire file buffered in memory
329
+ # first. Similar to send_data, but where the data is generated live.
302
330
  #
303
331
  # Options:
304
- # * <tt>:filename</tt> - suggests a filename for the browser to use.
305
- # * <tt>:type</tt> - specifies an HTTP content type.
306
- # You can specify either a string or a symbol for a registered type with <tt>Mime::Type.register</tt>, for example :json.
307
- # If omitted, type will be inferred from the file extension specified in <tt>:filename</tt>.
308
- # If no content type is registered for the extension, the default type 'application/octet-stream' will be used.
309
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
310
- # Valid values are 'inline' and 'attachment' (default).
332
+ # * `:filename` - suggests a filename for the browser to use.
333
+ # * `:type` - specifies an HTTP content type. You can specify either a string
334
+ # or a symbol for a registered type with `Mime::Type.register`, for example
335
+ # :json. If omitted, type will be inferred from the file extension specified
336
+ # in `:filename`. If no content type is registered for the extension, the
337
+ # default type 'application/octet-stream' will be used.
338
+ # * `:disposition` - specifies whether the file will be shown inline or
339
+ # downloaded. Valid values are 'inline' and 'attachment' (default).
340
+ #
311
341
  #
312
342
  # Example of generating a csv export:
313
343
  #
314
- # send_stream(filename: "subscribers.csv") do |stream|
315
- # stream.write "email_address,updated_at\n"
344
+ # send_stream(filename: "subscribers.csv") do |stream|
345
+ # stream.write "email_address,updated_at\n"
316
346
  #
317
- # @subscribers.find_each do |subscriber|
318
- # stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
319
- # end
320
- # end
347
+ # @subscribers.find_each do |subscriber|
348
+ # stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
349
+ # end
350
+ # end
321
351
  def send_stream(filename:, disposition: "attachment", type: nil)
322
- response.headers["Content-Type"] =
323
- (type.is_a?(Symbol) ? Mime[type].to_s : type) ||
324
- Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete("."))&.to_s ||
325
- "application/octet-stream"
352
+ payload = { filename: filename, disposition: disposition, type: type }
353
+ ActiveSupport::Notifications.instrument("send_stream.action_controller", payload) do
354
+ response.headers["Content-Type"] =
355
+ (type.is_a?(Symbol) ? Mime[type].to_s : type) ||
356
+ Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete("."))&.to_s ||
357
+ "application/octet-stream"
326
358
 
327
- response.headers["Content-Disposition"] =
328
- ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
359
+ response.headers["Content-Disposition"] =
360
+ ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
329
361
 
330
- yield response.stream
362
+ yield response.stream
363
+ end
331
364
  ensure
332
365
  response.stream.close
333
366
  end
334
367
 
335
368
  private
336
- # Spawn a new thread to serve up the controller in. This is to get
337
- # around the fact that Rack isn't based around IOs and we need to use
338
- # a thread to stream data from the response bodies. Nobody should call
339
- # this method except in Rails internals. Seriously!
369
+ # Spawn a new thread to serve up the controller in. This is to get around the
370
+ # fact that Rack isn't based around IOs and we need to use a thread to stream
371
+ # data from the response bodies. Nobody should call this method except in Rails
372
+ # internals. Seriously!
340
373
  def new_controller_thread # :nodoc:
341
374
  Thread.new {
342
375
  t2 = Thread.current
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionController
4
6
  module Logging
5
7
  extend ActiveSupport::Concern
@@ -7,10 +9,10 @@ module ActionController
7
9
  module ClassMethods
8
10
  # Set a different log level per request.
9
11
  #
10
- # # Use the debug log level if a particular cookie is set.
11
- # class ApplicationController < ActionController::Base
12
- # log_at :debug, if: -> { cookies[:debug] }
13
- # end
12
+ # # Use the debug log level if a particular cookie is set.
13
+ # class ApplicationController < ActionController::Base
14
+ # log_at :debug, if: -> { cookies[:debug] }
15
+ # end
14
16
  #
15
17
  def log_at(level, **options)
16
18
  around_action ->(_, action) { logger.log_at(level, &action) }, **options