actionpack 7.0.8 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -520
  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 +119 -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 +15 -11
  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 +77 -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 +188 -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 +216 -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 +122 -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 +155 -125
  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 +52 -45
  71. data/lib/action_dispatch/http/filter_parameters.rb +18 -8
  72. data/lib/action_dispatch/http/filter_redirect.rb +15 -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 -34
  79. data/lib/action_dispatch/http/rack_cache.rb +4 -0
  80. data/lib/action_dispatch/http/request.rb +113 -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 +89 -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 +684 -638
  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 +105 -61
  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 +49 -22
  168. data/lib/action_pack/gem_version.rb +5 -3
  169. data/lib/action_pack/version.rb +3 -1
  170. data/lib/action_pack.rb +18 -17
  171. metadata +88 -29
@@ -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,44 @@ 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
+ # 1) Event. If specified, an event with this name will be dispatched on
81
+ # the browser.
82
+ # 2) Retry. The reconnection time in milliseconds used when attempting
83
+ # to send the event.
84
+ # 3) Id. If the connection dies while sending an SSE to the browser, then
85
+ # the server will receive a +Last-Event-ID+ header with value equal to +id+.
65
86
  #
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.
87
+ # After setting an option in the constructor of the SSE object, all future SSEs
88
+ # sent across the stream will use those options unless overridden.
68
89
  #
69
90
  # Example Usage:
70
91
  #
71
- # class MyController < ActionController::Base
72
- # include ActionController::Live
92
+ # class MyController < ActionController::Base
93
+ # include ActionController::Live
73
94
  #
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
95
+ # def index
96
+ # response.headers['Content-Type'] = 'text/event-stream'
97
+ # sse = SSE.new(response.stream, retry: 300, event: "event-name")
98
+ # sse.write({ name: 'John'})
99
+ # sse.write({ name: 'John'}, id: 10)
100
+ # sse.write({ name: 'John'}, id: 10, event: "other-event")
101
+ # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
102
+ # ensure
103
+ # sse.close
104
+ # end
83
105
  # end
84
- # end
85
106
  #
86
- # Note: SSEs are not currently supported by IE. However, they are supported
87
- # by Chrome, Firefox, Opera, and Safari.
107
+ # Note: SSEs are not currently supported by IE. However, they are supported by
108
+ # Chrome, Firefox, Opera, and Safari.
88
109
  class SSE
89
110
  PERMITTED_OPTIONS = %w( retry event id )
90
111
 
@@ -134,10 +155,9 @@ module ActionController
134
155
 
135
156
  # Ignore that the client has disconnected.
136
157
  #
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.
158
+ # If this value is `true`, calling `write` after the client disconnects will
159
+ # result in the written content being silently discarded. If this value is
160
+ # `false` (the default), a ClientDisconnected exception will be raised.
141
161
  attr_accessor :ignore_disconnect
142
162
 
143
163
  def initialize(response)
@@ -148,6 +168,12 @@ module ActionController
148
168
  @ignore_disconnect = false
149
169
  end
150
170
 
171
+ # ActionDispatch::Response delegates #to_ary to the internal
172
+ # ActionDispatch::Response::Buffer, defining #to_ary is an indicator that the
173
+ # response body can be buffered and/or cached by Rack middlewares, this is not
174
+ # the case for Live responses so we undefine it for this Buffer subclass.
175
+ undef_method :to_ary
176
+
151
177
  def write(string)
152
178
  unless @response.committed?
153
179
  @response.headers["Cache-Control"] ||= "no-cache"
@@ -160,21 +186,20 @@ module ActionController
160
186
  @buf.clear
161
187
 
162
188
  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.
189
+ # Raise ClientDisconnected, which is a RuntimeError (not an IOError), because
190
+ # that's more appropriate for something beyond the developer's control.
166
191
  raise ClientDisconnected, "client disconnected"
167
192
  end
168
193
  end
169
194
  end
170
195
 
171
- # Same as +write+ but automatically include a newline at the end of the string.
196
+ # Same as `write` but automatically include a newline at the end of the string.
172
197
  def writeln(string)
173
198
  write string.end_with?("\n") ? string : "#{string}\n"
174
199
  end
175
200
 
176
- # Write a 'close' event to the buffer; the producer/writing thread
177
- # uses this to notify us that it's finished supplying content.
201
+ # Write a 'close' event to the buffer; the producer/writing thread uses this to
202
+ # notify us that it's finished supplying content.
178
203
  #
179
204
  # See also #abort.
180
205
  def close
@@ -185,9 +210,8 @@ module ActionController
185
210
  end
186
211
  end
187
212
 
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.
213
+ # Inform the producer/writing thread that the client has disconnected; the
214
+ # reading thread is no longer interested in anything that's being written.
191
215
  #
192
216
  # See also #close.
193
217
  def abort
@@ -199,8 +223,8 @@ module ActionController
199
223
 
200
224
  # Is the client still connected and waiting for content?
201
225
  #
202
- # The result of calling `write` when this is `false` is determined
203
- # by `ignore_disconnect`.
226
+ # The result of calling `write` when this is `false` is determined by
227
+ # `ignore_disconnect`.
204
228
  def connected?
205
229
  !@aborted
206
230
  end
@@ -251,15 +275,15 @@ module ActionController
251
275
  locals = t1.keys.map { |key| [key, t1[key]] }
252
276
 
253
277
  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.
278
+ # This processes the action in a child thread. It lets us return the response
279
+ # code and headers back up the Rack stack, and still process the body in
280
+ # parallel with sending data to the client.
257
281
  new_controller_thread {
258
282
  ActiveSupport::Dependencies.interlock.running do
259
283
  t2 = Thread.current
260
284
 
261
- # Since we're processing the view in a different thread, copy the
262
- # thread locals from the main thread to the child thread. :'(
285
+ # Since we're processing the view in a different thread, copy the thread locals
286
+ # from the main thread to the child thread. :'(
263
287
  locals.each { |k, v| t2[k] = v }
264
288
  ActiveSupport::IsolatedExecutionState.share_with(t1)
265
289
 
@@ -297,46 +321,52 @@ module ActionController
297
321
  response.close if response
298
322
  end
299
323
 
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.
324
+ # Sends a stream to the browser, which is helpful when you're generating exports
325
+ # or other running data where you don't want the entire file buffered in memory
326
+ # first. Similar to send_data, but where the data is generated live.
302
327
  #
303
328
  # 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).
329
+ # * `:filename` - suggests a filename for the browser to use.
330
+ # * `:type` - specifies an HTTP content type. You can specify either a string
331
+ # or a symbol for a registered type with `Mime::Type.register`, for example
332
+ # :json. If omitted, type will be inferred from the file extension specified
333
+ # in `:filename`. If no content type is registered for the extension, the
334
+ # default type 'application/octet-stream' will be used.
335
+ # * `:disposition` - specifies whether the file will be shown inline or
336
+ # downloaded. Valid values are 'inline' and 'attachment' (default).
337
+ #
311
338
  #
312
339
  # Example of generating a csv export:
313
340
  #
314
- # send_stream(filename: "subscribers.csv") do |stream|
315
- # stream.write "email_address,updated_at\n"
341
+ # send_stream(filename: "subscribers.csv") do |stream|
342
+ # stream.write "email_address,updated_at\n"
316
343
  #
317
- # @subscribers.find_each do |subscriber|
318
- # stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
319
- # end
320
- # end
344
+ # @subscribers.find_each do |subscriber|
345
+ # stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
346
+ # end
347
+ # end
321
348
  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"
349
+ payload = { filename: filename, disposition: disposition, type: type }
350
+ ActiveSupport::Notifications.instrument("send_stream.action_controller", payload) do
351
+ response.headers["Content-Type"] =
352
+ (type.is_a?(Symbol) ? Mime[type].to_s : type) ||
353
+ Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete("."))&.to_s ||
354
+ "application/octet-stream"
326
355
 
327
- response.headers["Content-Disposition"] =
328
- ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
356
+ response.headers["Content-Disposition"] =
357
+ ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename)
329
358
 
330
- yield response.stream
359
+ yield response.stream
360
+ end
331
361
  ensure
332
362
  response.stream.close
333
363
  end
334
364
 
335
365
  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!
366
+ # Spawn a new thread to serve up the controller in. This is to get around the
367
+ # fact that Rack isn't based around IOs and we need to use a thread to stream
368
+ # data from the response bodies. Nobody should call this method except in Rails
369
+ # internals. Seriously!
340
370
  def new_controller_thread # :nodoc:
341
371
  Thread.new {
342
372
  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