actionpack 3.2.22.5 → 5.2.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (271) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +279 -603
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -297
  5. data/lib/abstract_controller/asset_paths.rb +4 -2
  6. data/lib/abstract_controller/base.rb +82 -52
  7. data/lib/abstract_controller/caching/fragments.rb +166 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +117 -103
  10. data/lib/abstract_controller/collector.rb +18 -7
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +65 -38
  13. data/lib/abstract_controller/logger.rb +3 -2
  14. data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
  15. data/lib/abstract_controller/rendering.rb +77 -129
  16. data/lib/abstract_controller/translation.rb +21 -3
  17. data/lib/abstract_controller/url_for.rb +9 -7
  18. data/lib/abstract_controller.rb +12 -13
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/base.rb +81 -40
  22. data/lib/action_controller/caching.rb +22 -62
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +30 -18
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +190 -47
  27. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  28. data/lib/action_controller/metal/cookies.rb +3 -3
  29. data/lib/action_controller/metal/data_streaming.rb +40 -65
  30. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  31. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  32. data/lib/action_controller/metal/exceptions.rb +19 -12
  33. data/lib/action_controller/metal/flash.rb +42 -9
  34. data/lib/action_controller/metal/force_ssl.rb +79 -19
  35. data/lib/action_controller/metal/head.rb +35 -10
  36. data/lib/action_controller/metal/helpers.rb +31 -21
  37. data/lib/action_controller/metal/http_authentication.rb +182 -134
  38. data/lib/action_controller/metal/implicit_render.rb +62 -8
  39. data/lib/action_controller/metal/instrumentation.rb +28 -26
  40. data/lib/action_controller/metal/live.rb +312 -0
  41. data/lib/action_controller/metal/mime_responds.rb +159 -163
  42. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  43. data/lib/action_controller/metal/params_wrapper.rb +146 -93
  44. data/lib/action_controller/metal/redirecting.rb +80 -56
  45. data/lib/action_controller/metal/renderers.rb +119 -47
  46. data/lib/action_controller/metal/rendering.rb +89 -32
  47. data/lib/action_controller/metal/request_forgery_protection.rb +373 -41
  48. data/lib/action_controller/metal/rescue.rb +9 -16
  49. data/lib/action_controller/metal/streaming.rb +39 -45
  50. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  51. data/lib/action_controller/metal/testing.rb +8 -29
  52. data/lib/action_controller/metal/url_for.rb +43 -32
  53. data/lib/action_controller/metal.rb +112 -106
  54. data/lib/action_controller/railtie.rb +56 -18
  55. data/lib/action_controller/railties/helpers.rb +24 -0
  56. data/lib/action_controller/renderer.rb +117 -0
  57. data/lib/action_controller/template_assertions.rb +11 -0
  58. data/lib/action_controller/test_case.rb +402 -347
  59. data/lib/action_controller.rb +31 -30
  60. data/lib/action_dispatch/http/cache.rb +133 -34
  61. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  62. data/lib/action_dispatch/http/filter_parameters.rb +40 -24
  63. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  64. data/lib/action_dispatch/http/headers.rb +117 -16
  65. data/lib/action_dispatch/http/mime_negotiation.rb +98 -33
  66. data/lib/action_dispatch/http/mime_type.rb +198 -146
  67. data/lib/action_dispatch/http/mime_types.rb +22 -7
  68. data/lib/action_dispatch/http/parameter_filter.rb +61 -49
  69. data/lib/action_dispatch/http/parameters.rb +94 -51
  70. data/lib/action_dispatch/http/rack_cache.rb +4 -3
  71. data/lib/action_dispatch/http/request.rb +262 -117
  72. data/lib/action_dispatch/http/response.rb +400 -86
  73. data/lib/action_dispatch/http/upload.rb +66 -29
  74. data/lib/action_dispatch/http/url.rb +232 -60
  75. data/lib/action_dispatch/journey/formatter.rb +189 -0
  76. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  77. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  78. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  79. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  80. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  81. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  82. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  83. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  84. data/lib/action_dispatch/journey/parser.rb +199 -0
  85. data/lib/action_dispatch/journey/parser.y +50 -0
  86. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  87. data/lib/action_dispatch/journey/path/pattern.rb +199 -0
  88. data/lib/action_dispatch/journey/route.rb +203 -0
  89. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  90. data/lib/action_dispatch/journey/router.rb +156 -0
  91. data/lib/action_dispatch/journey/routes.rb +82 -0
  92. data/lib/action_dispatch/journey/scanner.rb +64 -0
  93. data/lib/action_dispatch/journey/visitors.rb +268 -0
  94. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  95. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  96. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  97. data/lib/action_dispatch/journey.rb +7 -0
  98. data/lib/action_dispatch/middleware/callbacks.rb +17 -13
  99. data/lib/action_dispatch/middleware/cookies.rb +494 -162
  100. data/lib/action_dispatch/middleware/debug_exceptions.rb +176 -53
  101. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  102. data/lib/action_dispatch/middleware/exception_wrapper.rb +103 -38
  103. data/lib/action_dispatch/middleware/executor.rb +21 -0
  104. data/lib/action_dispatch/middleware/flash.rb +128 -91
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +43 -16
  106. data/lib/action_dispatch/middleware/reloader.rb +6 -83
  107. data/lib/action_dispatch/middleware/remote_ip.rb +151 -49
  108. data/lib/action_dispatch/middleware/request_id.rb +19 -15
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +38 -34
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -9
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +94 -44
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -4
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +36 -61
  114. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  115. data/lib/action_dispatch/middleware/stack.rb +33 -41
  116. data/lib/action_dispatch/middleware/static.rb +92 -48
  117. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +134 -5
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  136. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  138. data/lib/action_dispatch/railtie.rb +29 -8
  139. data/lib/action_dispatch/request/session.rb +234 -0
  140. data/lib/action_dispatch/request/utils.rb +78 -0
  141. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  142. data/lib/action_dispatch/routing/inspector.rb +225 -0
  143. data/lib/action_dispatch/routing/mapper.rb +1329 -582
  144. data/lib/action_dispatch/routing/polymorphic_routes.rb +237 -94
  145. data/lib/action_dispatch/routing/redirection.rb +120 -50
  146. data/lib/action_dispatch/routing/route_set.rb +545 -322
  147. data/lib/action_dispatch/routing/routes_proxy.rb +37 -7
  148. data/lib/action_dispatch/routing/url_for.rb +103 -34
  149. data/lib/action_dispatch/routing.rb +66 -99
  150. data/lib/action_dispatch/system_test_case.rb +147 -0
  151. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  152. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  153. data/lib/action_dispatch/system_testing/server.rb +31 -0
  154. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  158. data/lib/action_dispatch/testing/assertions/response.rb +53 -42
  159. data/lib/action_dispatch/testing/assertions/routing.rb +79 -74
  160. data/lib/action_dispatch/testing/assertions.rb +15 -9
  161. data/lib/action_dispatch/testing/integration.rb +361 -207
  162. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  163. data/lib/action_dispatch/testing/test_process.rb +28 -19
  164. data/lib/action_dispatch/testing/test_request.rb +30 -33
  165. data/lib/action_dispatch/testing/test_response.rb +35 -11
  166. data/lib/action_dispatch.rb +42 -32
  167. data/lib/action_pack/gem_version.rb +17 -0
  168. data/lib/action_pack/version.rb +7 -7
  169. data/lib/action_pack.rb +4 -2
  170. metadata +116 -175
  171. data/lib/abstract_controller/layouts.rb +0 -423
  172. data/lib/abstract_controller/view_paths.rb +0 -96
  173. data/lib/action_controller/caching/actions.rb +0 -185
  174. data/lib/action_controller/caching/fragments.rb +0 -127
  175. data/lib/action_controller/caching/pages.rb +0 -187
  176. data/lib/action_controller/caching/sweeping.rb +0 -97
  177. data/lib/action_controller/deprecated/integration_test.rb +0 -2
  178. data/lib/action_controller/deprecated/performance_test.rb +0 -1
  179. data/lib/action_controller/deprecated.rb +0 -3
  180. data/lib/action_controller/metal/compatibility.rb +0 -65
  181. data/lib/action_controller/metal/hide_actions.rb +0 -41
  182. data/lib/action_controller/metal/rack_delegation.rb +0 -26
  183. data/lib/action_controller/metal/responder.rb +0 -286
  184. data/lib/action_controller/metal/session_management.rb +0 -14
  185. data/lib/action_controller/middleware.rb +0 -39
  186. data/lib/action_controller/railties/paths.rb +0 -25
  187. data/lib/action_controller/record_identifier.rb +0 -85
  188. data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
  189. data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
  190. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
  191. data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
  192. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
  193. data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
  194. data/lib/action_controller/vendor/html-scanner.rb +0 -20
  195. data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
  196. data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
  197. data/lib/action_dispatch/middleware/head.rb +0 -18
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -75
  199. data/lib/action_dispatch/middleware/rescue.rb +0 -26
  200. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
  201. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
  202. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
  203. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
  204. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
  205. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
  206. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
  207. data/lib/action_dispatch/testing/assertions/dom.rb +0 -37
  208. data/lib/action_dispatch/testing/assertions/selector.rb +0 -435
  209. data/lib/action_dispatch/testing/assertions/tag.rb +0 -138
  210. data/lib/action_dispatch/testing/performance_test.rb +0 -10
  211. data/lib/action_view/asset_paths.rb +0 -142
  212. data/lib/action_view/base.rb +0 -220
  213. data/lib/action_view/buffers.rb +0 -43
  214. data/lib/action_view/context.rb +0 -36
  215. data/lib/action_view/flows.rb +0 -79
  216. data/lib/action_view/helpers/active_model_helper.rb +0 -50
  217. data/lib/action_view/helpers/asset_paths.rb +0 -7
  218. data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
  219. data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
  220. data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
  221. data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
  222. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
  223. data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
  224. data/lib/action_view/helpers/cache_helper.rb +0 -64
  225. data/lib/action_view/helpers/capture_helper.rb +0 -203
  226. data/lib/action_view/helpers/controller_helper.rb +0 -25
  227. data/lib/action_view/helpers/csrf_helper.rb +0 -32
  228. data/lib/action_view/helpers/date_helper.rb +0 -1062
  229. data/lib/action_view/helpers/debug_helper.rb +0 -40
  230. data/lib/action_view/helpers/form_helper.rb +0 -1486
  231. data/lib/action_view/helpers/form_options_helper.rb +0 -658
  232. data/lib/action_view/helpers/form_tag_helper.rb +0 -685
  233. data/lib/action_view/helpers/javascript_helper.rb +0 -110
  234. data/lib/action_view/helpers/number_helper.rb +0 -622
  235. data/lib/action_view/helpers/output_safety_helper.rb +0 -38
  236. data/lib/action_view/helpers/record_tag_helper.rb +0 -111
  237. data/lib/action_view/helpers/rendering_helper.rb +0 -92
  238. data/lib/action_view/helpers/sanitize_helper.rb +0 -259
  239. data/lib/action_view/helpers/tag_helper.rb +0 -167
  240. data/lib/action_view/helpers/text_helper.rb +0 -426
  241. data/lib/action_view/helpers/translation_helper.rb +0 -91
  242. data/lib/action_view/helpers/url_helper.rb +0 -693
  243. data/lib/action_view/helpers.rb +0 -60
  244. data/lib/action_view/locale/en.yml +0 -160
  245. data/lib/action_view/log_subscriber.rb +0 -28
  246. data/lib/action_view/lookup_context.rb +0 -258
  247. data/lib/action_view/path_set.rb +0 -101
  248. data/lib/action_view/railtie.rb +0 -55
  249. data/lib/action_view/renderer/abstract_renderer.rb +0 -41
  250. data/lib/action_view/renderer/partial_renderer.rb +0 -415
  251. data/lib/action_view/renderer/renderer.rb +0 -61
  252. data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
  253. data/lib/action_view/renderer/template_renderer.rb +0 -95
  254. data/lib/action_view/template/error.rb +0 -128
  255. data/lib/action_view/template/handlers/builder.rb +0 -26
  256. data/lib/action_view/template/handlers/erb.rb +0 -125
  257. data/lib/action_view/template/handlers.rb +0 -50
  258. data/lib/action_view/template/resolver.rb +0 -298
  259. data/lib/action_view/template/text.rb +0 -30
  260. data/lib/action_view/template.rb +0 -337
  261. data/lib/action_view/test_case.rb +0 -246
  262. data/lib/action_view/testing/resolvers.rb +0 -49
  263. data/lib/action_view.rb +0 -84
  264. data/lib/sprockets/assets.rake +0 -99
  265. data/lib/sprockets/bootstrap.rb +0 -37
  266. data/lib/sprockets/compressors.rb +0 -83
  267. data/lib/sprockets/helpers/isolated_helper.rb +0 -13
  268. data/lib/sprockets/helpers/rails_helper.rb +0 -182
  269. data/lib/sprockets/helpers.rb +0 -6
  270. data/lib/sprockets/railtie.rb +0 -62
  271. data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,9 +1,11 @@
1
- require 'benchmark'
2
- require 'abstract_controller/logger'
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require "abstract_controller/logger"
3
5
 
4
6
  module ActionController
5
7
  # Adds instrumentation to several ends in ActionController::Base. It also provides
6
- # some hooks related with process_action, this allows an ORM like Active Record
8
+ # some hooks related with process_action. This allows an ORM like Active Record
7
9
  # and/or DataMapper to plug in ActionController and show related information.
8
10
  #
9
11
  # Check ActiveRecord::Railties::ControllerRuntime for an example.
@@ -16,21 +18,25 @@ module ActionController
16
18
 
17
19
  def process_action(*args)
18
20
  raw_payload = {
19
- :controller => self.class.name,
20
- :action => self.action_name,
21
- :params => request.filtered_parameters,
22
- :format => request.format.try(:ref),
23
- :method => request.method,
24
- :path => (request.fullpath rescue "unknown")
21
+ controller: self.class.name,
22
+ action: action_name,
23
+ params: request.filtered_parameters,
24
+ headers: request.headers,
25
+ format: request.format.ref,
26
+ method: request.request_method,
27
+ path: request.fullpath
25
28
  }
26
29
 
27
30
  ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
28
31
 
29
32
  ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
30
- result = super
31
- payload[:status] = response.status
32
- append_info_to_payload(payload)
33
- result
33
+ begin
34
+ result = super
35
+ payload[:status] = response.status
36
+ result
37
+ ensure
38
+ append_info_to_payload(payload)
39
+ end
34
40
  end
35
41
  end
36
42
 
@@ -42,9 +48,9 @@ module ActionController
42
48
  render_output
43
49
  end
44
50
 
45
- def send_file(path, options={})
51
+ def send_file(path, options = {})
46
52
  ActiveSupport::Notifications.instrument("send_file.action_controller",
47
- options.merge(:path => path)) do
53
+ options.merge(path: path)) do
48
54
  super
49
55
  end
50
56
  end
@@ -59,34 +65,31 @@ module ActionController
59
65
  ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
60
66
  result = super
61
67
  payload[:status] = response.status
62
- payload[:location] = response.location
68
+ payload[:location] = response.filtered_location
63
69
  result
64
70
  end
65
71
  end
66
72
 
67
73
  private
68
74
 
69
- # A hook invoked everytime a before callback is halted.
75
+ # A hook invoked every time a before callback is halted.
70
76
  def halted_callback_hook(filter)
71
- ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
77
+ ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
72
78
  end
73
79
 
74
- # A hook which allows you to clean up any time taken into account in
75
- # views wrongly, like database querying time.
80
+ # A hook which allows you to clean up any time, wrongly taken into account in
81
+ # views, like database querying time.
76
82
  #
77
83
  # def cleanup_view_runtime
78
84
  # super - time_taken_in_something_expensive
79
85
  # end
80
- #
81
- # :api: plugin
82
- def cleanup_view_runtime #:nodoc:
86
+ def cleanup_view_runtime # :doc:
83
87
  yield
84
88
  end
85
89
 
86
90
  # Every time after an action is processed, this method is invoked
87
91
  # with the payload, so you can add more information.
88
- # :api: plugin
89
- def append_info_to_payload(payload) #:nodoc:
92
+ def append_info_to_payload(payload) # :doc:
90
93
  payload[:view_runtime] = view_runtime
91
94
  end
92
95
 
@@ -94,7 +97,6 @@ module ActionController
94
97
  # A hook which allows other frameworks to log what happened during
95
98
  # controller process action. This method should return an array
96
99
  # with the messages to be added.
97
- # :api: plugin
98
100
  def log_process_action(payload) #:nodoc:
99
101
  messages, view_runtime = [], payload[:view_runtime]
100
102
  messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
@@ -0,0 +1,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/http/response"
4
+ require "delegate"
5
+ require "active_support/json"
6
+
7
+ 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
+ #
11
+ # class MyController < ActionController::Base
12
+ # include ActionController::Live
13
+ #
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
22
+ # end
23
+ # end
24
+ #
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.
30
+ #
31
+ # You *must* call close on your stream when you're finished, otherwise the
32
+ # socket may be left open forever.
33
+ #
34
+ # 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).
37
+ module Live
38
+ extend ActiveSupport::Concern
39
+
40
+ module ClassMethods
41
+ def make_response!(request)
42
+ if request.get_header("HTTP_VERSION") == "HTTP/1.0"
43
+ super
44
+ else
45
+ Live::Response.new.tap do |res|
46
+ res.request = request
47
+ end
48
+ end
49
+ end
50
+ end
51
+
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.
55
+ #
56
+ # Writing an object will convert it into standard SSE format with whatever
57
+ # options you have configured. You may choose to set the following options:
58
+ #
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+.
65
+ #
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.
68
+ #
69
+ # Example Usage:
70
+ #
71
+ # class MyController < ActionController::Base
72
+ # include ActionController::Live
73
+ #
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
83
+ # end
84
+ # end
85
+ #
86
+ # Note: SSEs are not currently supported by IE. However, they are supported
87
+ # by Chrome, Firefox, Opera, and Safari.
88
+ class SSE
89
+ WHITELISTED_OPTIONS = %w( retry event id )
90
+
91
+ def initialize(stream, options = {})
92
+ @stream = stream
93
+ @options = options
94
+ end
95
+
96
+ def close
97
+ @stream.close
98
+ end
99
+
100
+ def write(object, options = {})
101
+ case object
102
+ when String
103
+ perform_write(object, options)
104
+ else
105
+ perform_write(ActiveSupport::JSON.encode(object), options)
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def perform_write(json, options)
112
+ current_options = @options.merge(options).stringify_keys
113
+
114
+ WHITELISTED_OPTIONS.each do |option_name|
115
+ if (option_value = current_options[option_name])
116
+ @stream.write "#{option_name}: #{option_value}\n"
117
+ end
118
+ end
119
+
120
+ message = json.gsub("\n".freeze, "\ndata: ".freeze)
121
+ @stream.write "data: #{message}\n\n"
122
+ end
123
+ end
124
+
125
+ class ClientDisconnected < RuntimeError
126
+ end
127
+
128
+ class Buffer < ActionDispatch::Response::Buffer #:nodoc:
129
+ include MonitorMixin
130
+
131
+ # Ignore that the client has disconnected.
132
+ #
133
+ # If this value is `true`, calling `write` after the client
134
+ # disconnects will result in the written content being silently
135
+ # discarded. If this value is `false` (the default), a
136
+ # ClientDisconnected exception will be raised.
137
+ attr_accessor :ignore_disconnect
138
+
139
+ def initialize(response)
140
+ @error_callback = lambda { true }
141
+ @cv = new_cond
142
+ @aborted = false
143
+ @ignore_disconnect = false
144
+ super(response, SizedQueue.new(10))
145
+ end
146
+
147
+ def write(string)
148
+ unless @response.committed?
149
+ @response.set_header "Cache-Control", "no-cache"
150
+ @response.delete_header "Content-Length"
151
+ end
152
+
153
+ super
154
+
155
+ unless connected?
156
+ @buf.clear
157
+
158
+ unless @ignore_disconnect
159
+ # Raise ClientDisconnected, which is a RuntimeError (not an
160
+ # IOError), because that's more appropriate for something beyond
161
+ # the developer's control.
162
+ raise ClientDisconnected, "client disconnected"
163
+ end
164
+ end
165
+ end
166
+
167
+ # Write a 'close' event to the buffer; the producer/writing thread
168
+ # uses this to notify us that it's finished supplying content.
169
+ #
170
+ # See also #abort.
171
+ def close
172
+ synchronize do
173
+ super
174
+ @buf.push nil
175
+ @cv.broadcast
176
+ end
177
+ end
178
+
179
+ # Inform the producer/writing thread that the client has
180
+ # disconnected; the reading thread is no longer interested in
181
+ # anything that's being written.
182
+ #
183
+ # See also #close.
184
+ def abort
185
+ synchronize do
186
+ @aborted = true
187
+ @buf.clear
188
+ end
189
+ end
190
+
191
+ # Is the client still connected and waiting for content?
192
+ #
193
+ # The result of calling `write` when this is `false` is determined
194
+ # by `ignore_disconnect`.
195
+ def connected?
196
+ !@aborted
197
+ end
198
+
199
+ def on_error(&block)
200
+ @error_callback = block
201
+ end
202
+
203
+ def call_on_error
204
+ @error_callback.call
205
+ end
206
+
207
+ private
208
+
209
+ def each_chunk(&block)
210
+ loop do
211
+ str = nil
212
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
213
+ str = @buf.pop
214
+ end
215
+ break unless str
216
+ yield str
217
+ end
218
+ end
219
+ end
220
+
221
+ class Response < ActionDispatch::Response #:nodoc: all
222
+ private
223
+
224
+ def before_committed
225
+ super
226
+ jar = request.cookie_jar
227
+ # The response can be committed multiple times
228
+ jar.write self unless committed?
229
+ end
230
+
231
+ def build_buffer(response, body)
232
+ buf = Live::Buffer.new response
233
+ body.each { |part| buf.write part }
234
+ buf
235
+ end
236
+ end
237
+
238
+ def process(name)
239
+ t1 = Thread.current
240
+ locals = t1.keys.map { |key| [key, t1[key]] }
241
+
242
+ error = nil
243
+ # This processes the action in a child thread. It lets us return the
244
+ # response code and headers back up the Rack stack, and still process
245
+ # the body in parallel with sending data to the client.
246
+ new_controller_thread {
247
+ ActiveSupport::Dependencies.interlock.running do
248
+ t2 = Thread.current
249
+
250
+ # Since we're processing the view in a different thread, copy the
251
+ # thread locals from the main thread to the child thread. :'(
252
+ locals.each { |k, v| t2[k] = v }
253
+
254
+ begin
255
+ super(name)
256
+ rescue => e
257
+ if @_response.committed?
258
+ begin
259
+ @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
260
+ @_response.stream.call_on_error
261
+ rescue => exception
262
+ log_error(exception)
263
+ ensure
264
+ log_error(e)
265
+ @_response.stream.close
266
+ end
267
+ else
268
+ error = e
269
+ end
270
+ ensure
271
+ @_response.commit!
272
+ end
273
+ end
274
+ }
275
+
276
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
277
+ @_response.await_commit
278
+ end
279
+
280
+ raise error if error
281
+ end
282
+
283
+ # Spawn a new thread to serve up the controller in. This is to get
284
+ # around the fact that Rack isn't based around IOs and we need to use
285
+ # a thread to stream data from the response bodies. Nobody should call
286
+ # this method except in Rails internals. Seriously!
287
+ def new_controller_thread # :nodoc:
288
+ Thread.new {
289
+ t2 = Thread.current
290
+ t2.abort_on_exception = true
291
+ yield
292
+ }
293
+ end
294
+
295
+ def log_error(exception)
296
+ logger = ActionController::Base.logger
297
+ return unless logger
298
+
299
+ logger.fatal do
300
+ message = "\n#{exception.class} (#{exception.message}):\n".dup
301
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
302
+ message << " " << exception.backtrace.join("\n ")
303
+ "#{message}\n\n"
304
+ end
305
+ end
306
+
307
+ def response_body=(body)
308
+ super
309
+ response.close if response
310
+ end
311
+ end
312
+ end