actionpack 5.2.3

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 (170) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +429 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller.rb +27 -0
  6. data/lib/abstract_controller/asset_paths.rb +12 -0
  7. data/lib/abstract_controller/base.rb +265 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/caching/fragments.rb +166 -0
  10. data/lib/abstract_controller/callbacks.rb +212 -0
  11. data/lib/abstract_controller/collector.rb +43 -0
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +194 -0
  14. data/lib/abstract_controller/logger.rb +14 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +20 -0
  16. data/lib/abstract_controller/rendering.rb +127 -0
  17. data/lib/abstract_controller/translation.rb +31 -0
  18. data/lib/abstract_controller/url_for.rb +35 -0
  19. data/lib/action_controller.rb +66 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +276 -0
  23. data/lib/action_controller/caching.rb +46 -0
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +78 -0
  26. data/lib/action_controller/metal.rb +256 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +274 -0
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +16 -0
  31. data/lib/action_controller/metal/data_streaming.rb +152 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  34. data/lib/action_controller/metal/exceptions.rb +53 -0
  35. data/lib/action_controller/metal/flash.rb +61 -0
  36. data/lib/action_controller/metal/force_ssl.rb +99 -0
  37. data/lib/action_controller/metal/head.rb +60 -0
  38. data/lib/action_controller/metal/helpers.rb +123 -0
  39. data/lib/action_controller/metal/http_authentication.rb +519 -0
  40. data/lib/action_controller/metal/implicit_render.rb +73 -0
  41. data/lib/action_controller/metal/instrumentation.rb +107 -0
  42. data/lib/action_controller/metal/live.rb +312 -0
  43. data/lib/action_controller/metal/mime_responds.rb +313 -0
  44. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  45. data/lib/action_controller/metal/params_wrapper.rb +293 -0
  46. data/lib/action_controller/metal/redirecting.rb +133 -0
  47. data/lib/action_controller/metal/renderers.rb +181 -0
  48. data/lib/action_controller/metal/rendering.rb +122 -0
  49. data/lib/action_controller/metal/request_forgery_protection.rb +445 -0
  50. data/lib/action_controller/metal/rescue.rb +28 -0
  51. data/lib/action_controller/metal/streaming.rb +223 -0
  52. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  53. data/lib/action_controller/metal/testing.rb +16 -0
  54. data/lib/action_controller/metal/url_for.rb +58 -0
  55. data/lib/action_controller/railtie.rb +89 -0
  56. data/lib/action_controller/railties/helpers.rb +24 -0
  57. data/lib/action_controller/renderer.rb +117 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +629 -0
  60. data/lib/action_dispatch.rb +112 -0
  61. data/lib/action_dispatch/http/cache.rb +222 -0
  62. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  63. data/lib/action_dispatch/http/filter_parameters.rb +84 -0
  64. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  65. data/lib/action_dispatch/http/headers.rb +132 -0
  66. data/lib/action_dispatch/http/mime_negotiation.rb +175 -0
  67. data/lib/action_dispatch/http/mime_type.rb +342 -0
  68. data/lib/action_dispatch/http/mime_types.rb +50 -0
  69. data/lib/action_dispatch/http/parameter_filter.rb +86 -0
  70. data/lib/action_dispatch/http/parameters.rb +126 -0
  71. data/lib/action_dispatch/http/rack_cache.rb +63 -0
  72. data/lib/action_dispatch/http/request.rb +430 -0
  73. data/lib/action_dispatch/http/response.rb +519 -0
  74. data/lib/action_dispatch/http/upload.rb +84 -0
  75. data/lib/action_dispatch/http/url.rb +350 -0
  76. data/lib/action_dispatch/journey.rb +7 -0
  77. data/lib/action_dispatch/journey/formatter.rb +189 -0
  78. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  81. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  82. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  85. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  86. data/lib/action_dispatch/journey/parser.rb +199 -0
  87. data/lib/action_dispatch/journey/parser.y +50 -0
  88. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  89. data/lib/action_dispatch/journey/path/pattern.rb +198 -0
  90. data/lib/action_dispatch/journey/route.rb +203 -0
  91. data/lib/action_dispatch/journey/router.rb +156 -0
  92. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  93. data/lib/action_dispatch/journey/routes.rb +82 -0
  94. data/lib/action_dispatch/journey/scanner.rb +64 -0
  95. data/lib/action_dispatch/journey/visitors.rb +268 -0
  96. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  97. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  98. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  99. data/lib/action_dispatch/middleware/callbacks.rb +36 -0
  100. data/lib/action_dispatch/middleware/cookies.rb +685 -0
  101. data/lib/action_dispatch/middleware/debug_exceptions.rb +205 -0
  102. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  103. data/lib/action_dispatch/middleware/exception_wrapper.rb +147 -0
  104. data/lib/action_dispatch/middleware/executor.rb +21 -0
  105. data/lib/action_dispatch/middleware/flash.rb +300 -0
  106. data/lib/action_dispatch/middleware/public_exceptions.rb +57 -0
  107. data/lib/action_dispatch/middleware/reloader.rb +12 -0
  108. data/lib/action_dispatch/middleware/remote_ip.rb +183 -0
  109. data/lib/action_dispatch/middleware/request_id.rb +43 -0
  110. data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
  111. data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
  112. data/lib/action_dispatch/middleware/session/cookie_store.rb +118 -0
  113. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
  114. data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
  115. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  116. data/lib/action_dispatch/middleware/stack.rb +116 -0
  117. data/lib/action_dispatch/middleware/static.rb +130 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +161 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  138. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  139. data/lib/action_dispatch/railtie.rb +55 -0
  140. data/lib/action_dispatch/request/session.rb +234 -0
  141. data/lib/action_dispatch/request/utils.rb +78 -0
  142. data/lib/action_dispatch/routing.rb +260 -0
  143. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  144. data/lib/action_dispatch/routing/inspector.rb +225 -0
  145. data/lib/action_dispatch/routing/mapper.rb +2267 -0
  146. data/lib/action_dispatch/routing/polymorphic_routes.rb +352 -0
  147. data/lib/action_dispatch/routing/redirection.rb +201 -0
  148. data/lib/action_dispatch/routing/route_set.rb +890 -0
  149. data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
  150. data/lib/action_dispatch/routing/url_for.rb +236 -0
  151. data/lib/action_dispatch/system_test_case.rb +147 -0
  152. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  153. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  154. data/lib/action_dispatch/system_testing/server.rb +31 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  158. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  159. data/lib/action_dispatch/testing/assertions.rb +24 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +107 -0
  161. data/lib/action_dispatch/testing/assertions/routing.rb +222 -0
  162. data/lib/action_dispatch/testing/integration.rb +652 -0
  163. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  164. data/lib/action_dispatch/testing/test_process.rb +50 -0
  165. data/lib/action_dispatch/testing/test_request.rb +71 -0
  166. data/lib/action_dispatch/testing/test_response.rb +53 -0
  167. data/lib/action_pack.rb +26 -0
  168. data/lib/action_pack/gem_version.rb +17 -0
  169. data/lib/action_pack/version.rb +10 -0
  170. metadata +318 -0
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ # Handles implicit rendering for a controller action that does not
5
+ # explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
6
+ #
7
+ # For API controllers, the implicit response is always <tt>204 No Content</tt>.
8
+ #
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>:
12
+ #
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).
16
+ #
17
+ # 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.
21
+ #
22
+ # 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>ActionView::UnknownFormat</tt> with an explanation.
26
+ #
27
+ # 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>.
29
+ module ImplicitRender
30
+ # :stopdoc:
31
+ include BasicImplicitRender
32
+
33
+ def default_render(*args)
34
+ if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
35
+ render(*args)
36
+ elsif any_templates?(action_name.to_s, _prefixes)
37
+ message = "#{self.class.name}\##{action_name} is missing a template " \
38
+ "for this request format and variant.\n" \
39
+ "\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \
40
+ "\nrequest.variant: #{request.variant.inspect}"
41
+
42
+ raise ActionController::UnknownFormat, message
43
+ elsif interactive_browser_request?
44
+ message = "#{self.class.name}\##{action_name} is missing a template " \
45
+ "for this request format and variant.\n\n" \
46
+ "request.formats: #{request.formats.map(&:to_s).inspect}\n" \
47
+ "request.variant: #{request.variant.inspect}\n\n" \
48
+ "NOTE! For XHR/Ajax or API requests, this action would normally " \
49
+ "respond with 204 No Content: an empty white screen. Since you're " \
50
+ "loading it in a web browser, we assume that you expected to " \
51
+ "actually render a template, not nothing, so we're showing an " \
52
+ "error to be extra-clear. If you expect 204 No Content, carry on. " \
53
+ "That's what you'll get from an XHR or API request. Give it a shot."
54
+
55
+ raise ActionController::UnknownFormat, message
56
+ else
57
+ logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
58
+ super
59
+ end
60
+ end
61
+
62
+ def method_for_action(action_name)
63
+ super || if template_exists?(action_name.to_s, _prefixes)
64
+ "default_render"
65
+ end
66
+ end
67
+
68
+ private
69
+ def interactive_browser_request?
70
+ request.get? && request.format == Mime[:html] && !request.xhr?
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+ require "abstract_controller/logger"
5
+
6
+ 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.
10
+ #
11
+ # Check ActiveRecord::Railties::ControllerRuntime for an example.
12
+ module Instrumentation
13
+ extend ActiveSupport::Concern
14
+
15
+ include AbstractController::Logger
16
+
17
+ attr_internal :view_runtime
18
+
19
+ def process_action(*args)
20
+ raw_payload = {
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
28
+ }
29
+
30
+ ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
31
+
32
+ ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
33
+ begin
34
+ result = super
35
+ payload[:status] = response.status
36
+ result
37
+ ensure
38
+ append_info_to_payload(payload)
39
+ end
40
+ end
41
+ end
42
+
43
+ def render(*args)
44
+ render_output = nil
45
+ self.view_runtime = cleanup_view_runtime do
46
+ Benchmark.ms { render_output = super }
47
+ end
48
+ render_output
49
+ end
50
+
51
+ def send_file(path, options = {})
52
+ ActiveSupport::Notifications.instrument("send_file.action_controller",
53
+ options.merge(path: path)) do
54
+ super
55
+ end
56
+ end
57
+
58
+ def send_data(data, options = {})
59
+ ActiveSupport::Notifications.instrument("send_data.action_controller", options) do
60
+ super
61
+ end
62
+ end
63
+
64
+ def redirect_to(*args)
65
+ ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
66
+ result = super
67
+ payload[:status] = response.status
68
+ payload[:location] = response.filtered_location
69
+ result
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ # A hook invoked every time a before callback is halted.
76
+ def halted_callback_hook(filter)
77
+ ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
78
+ end
79
+
80
+ # A hook which allows you to clean up any time, wrongly taken into account in
81
+ # views, like database querying time.
82
+ #
83
+ # def cleanup_view_runtime
84
+ # super - time_taken_in_something_expensive
85
+ # end
86
+ def cleanup_view_runtime # :doc:
87
+ yield
88
+ end
89
+
90
+ # Every time after an action is processed, this method is invoked
91
+ # with the payload, so you can add more information.
92
+ def append_info_to_payload(payload) # :doc:
93
+ payload[:view_runtime] = view_runtime
94
+ end
95
+
96
+ module ClassMethods
97
+ # A hook which allows other frameworks to log what happened during
98
+ # controller process action. This method should return an array
99
+ # with the messages to be added.
100
+ def log_process_action(payload) #:nodoc:
101
+ messages, view_runtime = [], payload[:view_runtime]
102
+ messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
103
+ messages
104
+ end
105
+ end
106
+ end
107
+ end
@@ -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