actionpack 4.2.10 → 7.2.0.rc1

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.

Potentially problematic release.


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

Files changed (202) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +86 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -14
  5. data/lib/abstract_controller/asset_paths.rb +5 -1
  6. data/lib/abstract_controller/base.rb +166 -136
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +126 -57
  10. data/lib/abstract_controller/collector.rb +13 -15
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +181 -132
  14. data/lib/abstract_controller/logger.rb +5 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
  16. data/lib/abstract_controller/rendering.rb +56 -56
  17. data/lib/abstract_controller/translation.rb +29 -15
  18. data/lib/abstract_controller/url_for.rb +15 -11
  19. data/lib/abstract_controller.rb +21 -5
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +154 -0
  22. data/lib/action_controller/base.rb +219 -155
  23. data/lib/action_controller/caching.rb +28 -68
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +35 -22
  27. data/lib/action_controller/metal/allow_browser.rb +119 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +259 -122
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +9 -5
  32. data/lib/action_controller/metal/data_streaming.rb +87 -104
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
  36. data/lib/action_controller/metal/exceptions.rb +71 -24
  37. data/lib/action_controller/metal/flash.rb +26 -19
  38. data/lib/action_controller/metal/head.rb +45 -36
  39. data/lib/action_controller/metal/helpers.rb +80 -64
  40. data/lib/action_controller/metal/http_authentication.rb +297 -244
  41. data/lib/action_controller/metal/implicit_render.rb +57 -9
  42. data/lib/action_controller/metal/instrumentation.rb +76 -64
  43. data/lib/action_controller/metal/live.rb +238 -176
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +177 -166
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +145 -118
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +203 -64
  51. data/lib/action_controller/metal/renderers.rb +108 -65
  52. data/lib/action_controller/metal/rendering.rb +216 -56
  53. data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
  54. data/lib/action_controller/metal/rescue.rb +19 -21
  55. data/lib/action_controller/metal/streaming.rb +179 -138
  56. data/lib/action_controller/metal/strong_parameters.rb +1058 -382
  57. data/lib/action_controller/metal/testing.rb +11 -17
  58. data/lib/action_controller/metal/url_for.rb +37 -21
  59. data/lib/action_controller/metal.rb +236 -138
  60. data/lib/action_controller/railtie.rb +89 -11
  61. data/lib/action_controller/railties/helpers.rb +5 -1
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +425 -497
  65. data/lib/action_controller.rb +44 -22
  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 +119 -63
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +364 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +36 -34
  72. data/lib/action_dispatch/http/filter_redirect.rb +24 -12
  73. data/lib/action_dispatch/http/headers.rb +66 -31
  74. data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
  75. data/lib/action_dispatch/http/mime_type.rb +196 -136
  76. data/lib/action_dispatch/http/mime_types.rb +25 -7
  77. data/lib/action_dispatch/http/parameters.rb +97 -45
  78. data/lib/action_dispatch/http/permissions_policy.rb +187 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +6 -0
  80. data/lib/action_dispatch/http/request.rb +299 -170
  81. data/lib/action_dispatch/http/response.rb +311 -160
  82. data/lib/action_dispatch/http/upload.rb +52 -23
  83. data/lib/action_dispatch/http/url.rb +201 -125
  84. data/lib/action_dispatch/journey/formatter.rb +110 -50
  85. data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
  88. data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
  89. data/lib/action_dispatch/journey/nodes/node.rb +100 -20
  90. data/lib/action_dispatch/journey/parser.rb +19 -17
  91. data/lib/action_dispatch/journey/parser.y +4 -3
  92. data/lib/action_dispatch/journey/parser_extras.rb +14 -4
  93. data/lib/action_dispatch/journey/path/pattern.rb +79 -63
  94. data/lib/action_dispatch/journey/route.rb +108 -44
  95. data/lib/action_dispatch/journey/router/utils.rb +41 -29
  96. data/lib/action_dispatch/journey/router.rb +64 -57
  97. data/lib/action_dispatch/journey/routes.rb +23 -21
  98. data/lib/action_dispatch/journey/scanner.rb +28 -17
  99. data/lib/action_dispatch/journey/visitors.rb +100 -54
  100. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  101. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  102. data/lib/action_dispatch/journey.rb +7 -5
  103. data/lib/action_dispatch/log_subscriber.rb +25 -0
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  106. data/lib/action_dispatch/middleware/callbacks.rb +7 -6
  107. data/lib/action_dispatch/middleware/cookies.rb +471 -328
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
  109. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  110. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
  112. data/lib/action_dispatch/middleware/executor.rb +32 -0
  113. data/lib/action_dispatch/middleware/flash.rb +143 -101
  114. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
  116. data/lib/action_dispatch/middleware/reloader.rb +10 -92
  117. data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
  118. data/lib/action_dispatch/middleware/request_id.rb +29 -15
  119. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
  125. data/lib/action_dispatch/middleware/ssl.rb +134 -36
  126. data/lib/action_dispatch/middleware/stack.rb +109 -44
  127. data/lib/action_dispatch/middleware/static.rb +159 -90
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
  132. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
  146. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
  147. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
  148. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  149. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  150. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  151. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
  152. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
  153. data/lib/action_dispatch/railtie.rb +44 -16
  154. data/lib/action_dispatch/request/session.rb +159 -69
  155. data/lib/action_dispatch/request/utils.rb +97 -23
  156. data/lib/action_dispatch/routing/endpoint.rb +11 -2
  157. data/lib/action_dispatch/routing/inspector.rb +195 -106
  158. data/lib/action_dispatch/routing/mapper.rb +1338 -955
  159. data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
  160. data/lib/action_dispatch/routing/redirection.rb +78 -51
  161. data/lib/action_dispatch/routing/route_set.rb +460 -374
  162. data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
  163. data/lib/action_dispatch/routing/url_for.rb +172 -124
  164. data/lib/action_dispatch/routing.rb +159 -158
  165. data/lib/action_dispatch/system_test_case.rb +206 -0
  166. data/lib/action_dispatch/system_testing/browser.rb +84 -0
  167. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  168. data/lib/action_dispatch/system_testing/server.rb +33 -0
  169. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  170. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  171. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  172. data/lib/action_dispatch/testing/assertions/response.rb +71 -39
  173. data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
  174. data/lib/action_dispatch/testing/assertions.rb +9 -6
  175. data/lib/action_dispatch/testing/integration.rb +486 -306
  176. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  177. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  178. data/lib/action_dispatch/testing/test_process.rb +35 -22
  179. data/lib/action_dispatch/testing/test_request.rb +29 -34
  180. data/lib/action_dispatch/testing/test_response.rb +48 -15
  181. data/lib/action_dispatch.rb +82 -40
  182. data/lib/action_pack/gem_version.rb +8 -4
  183. data/lib/action_pack/version.rb +6 -2
  184. data/lib/action_pack.rb +21 -18
  185. metadata +146 -56
  186. data/lib/action_controller/caching/fragments.rb +0 -103
  187. data/lib/action_controller/metal/force_ssl.rb +0 -97
  188. data/lib/action_controller/metal/hide_actions.rb +0 -40
  189. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  190. data/lib/action_controller/middleware.rb +0 -39
  191. data/lib/action_controller/model_naming.rb +0 -12
  192. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  193. data/lib/action_dispatch/journey/backwards.rb +0 -5
  194. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  195. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  196. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  197. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  199. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
  200. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  201. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  202. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,110 +1,193 @@
1
- require 'action_dispatch/http/request'
2
- require 'action_dispatch/middleware/exception_wrapper'
3
- require 'action_dispatch/routing/inspector'
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "action_dispatch/middleware/exception_wrapper"
6
+ require "action_dispatch/routing/inspector"
7
+
8
+ require "action_view"
4
9
 
5
10
  module ActionDispatch
6
- # This middleware is responsible for logging exceptions and
7
- # showing a debugging page in case the request is local.
11
+ # # Action Dispatch DebugExceptions
12
+ #
13
+ # This middleware is responsible for logging exceptions and showing a debugging
14
+ # page in case the request is local.
8
15
  class DebugExceptions
9
- RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
16
+ cattr_reader :interceptors, instance_accessor: false, default: []
10
17
 
11
- def initialize(app, routes_app = nil)
12
- @app = app
13
- @routes_app = routes_app
18
+ def self.register_interceptor(object = nil, &block)
19
+ interceptor = object || block
20
+ interceptors << interceptor
21
+ end
22
+
23
+ def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
24
+ @app = app
25
+ @routes_app = routes_app
26
+ @response_format = response_format
27
+ @interceptors = interceptors
14
28
  end
15
29
 
16
30
  def call(env)
17
31
  _, headers, body = response = @app.call(env)
18
32
 
19
- if headers['X-Cascade'] == 'pass'
33
+ if headers[Constants::X_CASCADE] == "pass"
20
34
  body.close if body.respond_to?(:close)
21
35
  raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
22
36
  end
23
37
 
24
38
  response
25
39
  rescue Exception => exception
26
- raise exception if env['action_dispatch.show_exceptions'] == false
27
- render_exception(env, exception)
40
+ request = ActionDispatch::Request.new env
41
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
42
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
43
+
44
+ invoke_interceptors(request, exception, wrapper)
45
+ raise exception unless wrapper.show?(request)
46
+ render_exception(request, exception, wrapper)
28
47
  end
29
48
 
30
49
  private
31
-
32
- def render_exception(env, exception)
33
- wrapper = ExceptionWrapper.new(env, exception)
34
- log_error(env, wrapper)
35
-
36
- if env['action_dispatch.show_detailed_exceptions']
37
- request = Request.new(env)
38
- traces = wrapper.traces
39
-
40
- trace_to_show = 'Application Trace'
41
- if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error'
42
- trace_to_show = 'Full Trace'
50
+ def invoke_interceptors(request, exception, wrapper)
51
+ @interceptors.each do |interceptor|
52
+ interceptor.call(request, exception)
53
+ rescue Exception
54
+ log_error(request, wrapper)
43
55
  end
56
+ end
44
57
 
45
- if source_to_show = traces[trace_to_show].first
46
- source_to_show_id = source_to_show[:id]
58
+ def render_exception(request, exception, wrapper)
59
+ log_error(request, wrapper)
60
+
61
+ if request.get_header("action_dispatch.show_detailed_exceptions")
62
+ begin
63
+ content_type = request.formats.first
64
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
65
+ content_type = Mime[:text]
66
+ end
67
+
68
+ if api_request?(content_type)
69
+ render_for_api_request(content_type, wrapper)
70
+ else
71
+ render_for_browser_request(request, wrapper)
72
+ end
73
+ else
74
+ raise exception
47
75
  end
76
+ end
48
77
 
49
- template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
50
- request: request,
51
- exception: wrapper.exception,
52
- traces: traces,
53
- show_source_idx: source_to_show_id,
54
- trace_to_show: trace_to_show,
55
- routes_inspector: routes_inspector(exception),
56
- source_extracts: wrapper.source_extracts,
57
- line_number: wrapper.line_number,
58
- file: wrapper.file
59
- )
78
+ def render_for_browser_request(request, wrapper)
79
+ template = create_template(request, wrapper)
60
80
  file = "rescues/#{wrapper.rescue_template}"
61
81
 
62
82
  if request.xhr?
63
83
  body = template.render(template: file, layout: false, formats: [:text])
64
84
  format = "text/plain"
65
85
  else
66
- body = template.render(template: file, layout: 'rescues/layout')
86
+ body = template.render(template: file, layout: "rescues/layout")
67
87
  format = "text/html"
68
88
  end
69
89
  render(wrapper.status_code, body, format)
70
- else
71
- raise exception
72
90
  end
73
- end
74
91
 
75
- def render(status, body, format)
76
- [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
77
- end
92
+ def render_for_api_request(content_type, wrapper)
93
+ body = {
94
+ status: wrapper.status_code,
95
+ error: Rack::Utils::HTTP_STATUS_CODES.fetch(
96
+ wrapper.status_code,
97
+ Rack::Utils::HTTP_STATUS_CODES[500]
98
+ ),
99
+ exception: wrapper.exception_inspect,
100
+ traces: wrapper.traces
101
+ }
102
+
103
+ to_format = "to_#{content_type.to_sym}"
104
+
105
+ if content_type && body.respond_to?(to_format)
106
+ formatted_body = body.public_send(to_format)
107
+ format = content_type
108
+ else
109
+ formatted_body = body.to_json
110
+ format = Mime[:json]
111
+ end
78
112
 
79
- def log_error(env, wrapper)
80
- logger = logger(env)
81
- return unless logger
113
+ render(wrapper.status_code, formatted_body, format)
114
+ end
82
115
 
83
- exception = wrapper.exception
116
+ def create_template(request, wrapper)
117
+ DebugView.new(
118
+ request: request,
119
+ exception_wrapper: wrapper,
120
+ # Everything should use the wrapper, but we need to pass `exception` for legacy
121
+ # code.
122
+ exception: wrapper.exception,
123
+ traces: wrapper.traces,
124
+ show_source_idx: wrapper.source_to_show_id,
125
+ trace_to_show: wrapper.trace_to_show,
126
+ routes_inspector: routes_inspector(wrapper),
127
+ source_extracts: wrapper.source_extracts,
128
+ )
129
+ end
130
+
131
+ def render(status, body, format)
132
+ [status, { Rack::CONTENT_TYPE => "#{format}; charset=#{Response.default_charset}", Rack::CONTENT_LENGTH => body.bytesize.to_s }, [body]]
133
+ end
134
+
135
+ def log_error(request, wrapper)
136
+ logger = logger(request)
137
+
138
+ return unless logger
139
+ return if !log_rescued_responses?(request) && wrapper.rescue_response?
140
+
141
+ trace = wrapper.exception_trace
84
142
 
85
- trace = wrapper.application_trace
86
- trace = wrapper.framework_trace if trace.empty?
143
+ message = []
144
+ message << " "
145
+ message << "#{wrapper.exception_class_name} (#{wrapper.message}):"
146
+ if wrapper.has_cause?
147
+ message << "\nCauses:"
148
+ wrapper.wrapped_causes.each do |wrapped_cause|
149
+ message << "#{wrapped_cause.exception_class_name} (#{wrapped_cause.message})"
150
+ end
151
+ end
152
+ message.concat(wrapper.annotated_source_code)
153
+ message << " "
154
+ message.concat(trace)
87
155
 
88
- ActiveSupport::Deprecation.silence do
89
- message = "\n#{exception.class} (#{exception.message}):\n"
90
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
91
- message << " " << trace.join("\n ")
92
- logger.fatal("#{message}\n\n")
156
+ log_array(logger, message, request)
93
157
  end
94
- end
95
158
 
96
- def logger(env)
97
- env['action_dispatch.logger'] || stderr_logger
98
- end
159
+ def log_array(logger, lines, request)
160
+ return if lines.empty?
99
161
 
100
- def stderr_logger
101
- @stderr_logger ||= ActiveSupport::Logger.new($stderr)
102
- end
162
+ level = request.get_header("action_dispatch.debug_exception_log_level")
163
+
164
+ if logger.formatter && logger.formatter.respond_to?(:tags_text)
165
+ logger.add(level, lines.join("\n#{logger.formatter.tags_text}"))
166
+ else
167
+ logger.add(level, lines.join("\n"))
168
+ end
169
+ end
103
170
 
104
- def routes_inspector(exception)
105
- if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
106
- ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
171
+ def logger(request)
172
+ request.logger || ActionView::Base.logger || stderr_logger
173
+ end
174
+
175
+ def stderr_logger
176
+ @stderr_logger ||= ActiveSupport::Logger.new($stderr)
177
+ end
178
+
179
+ def routes_inspector(exception)
180
+ if @routes_app.respond_to?(:routes) && (exception.routing_error? || exception.template_error?)
181
+ ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
182
+ end
183
+ end
184
+
185
+ def api_request?(content_type)
186
+ @response_format == :api && !content_type.html?
187
+ end
188
+
189
+ def log_rescued_responses?(request)
190
+ request.get_header("action_dispatch.log_rescued_responses")
107
191
  end
108
- end
109
192
  end
110
193
  end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionDispatch
6
+ # # Action Dispatch DebugLocks
7
+ #
8
+ # This middleware can be used to diagnose deadlocks in the autoload interlock.
9
+ #
10
+ # To use it, insert it near the top of the middleware stack, using
11
+ # `config/application.rb`:
12
+ #
13
+ # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks
14
+ #
15
+ # After restarting the application and re-triggering the deadlock condition, the
16
+ # route `/rails/locks` will show a summary of all threads currently known to the
17
+ # interlock, which lock level they are holding or awaiting, and their current
18
+ # backtrace.
19
+ #
20
+ # Generally a deadlock will be caused by the interlock conflicting with some
21
+ # other external lock or blocking I/O call. These cannot be automatically
22
+ # identified, but should be visible in the displayed backtraces.
23
+ #
24
+ # NOTE: The formatting and content of this middleware's output is intended for
25
+ # human consumption, and should be expected to change between releases.
26
+ #
27
+ # This middleware exposes operational details of the server, with no access
28
+ # control. It should only be enabled when in use, and removed thereafter.
29
+ class DebugLocks
30
+ def initialize(app, path = "/rails/locks")
31
+ @app = app
32
+ @path = path
33
+ end
34
+
35
+ def call(env)
36
+ req = ActionDispatch::Request.new env
37
+
38
+ if req.get?
39
+ path = req.path_info.chomp("/")
40
+ if path == @path
41
+ return render_details(req)
42
+ end
43
+ end
44
+
45
+ @app.call(env)
46
+ end
47
+
48
+ private
49
+ def render_details(req)
50
+ threads = ActiveSupport::Dependencies.interlock.raw_state do |raw_threads|
51
+ # The Interlock itself comes to a complete halt as long as this block is
52
+ # executing. That gives us a more consistent picture of everything, but creates
53
+ # a pretty strong Observer Effect.
54
+ #
55
+ # Most directly, that means we need to do as little as possible in this block.
56
+ # More widely, it means this middleware should remain a strictly diagnostic tool
57
+ # (to be used when something has gone wrong), and not for any sort of general
58
+ # monitoring.
59
+
60
+ raw_threads.each.with_index do |(thread, info), idx|
61
+ info[:index] = idx
62
+ info[:backtrace] = thread.backtrace
63
+ end
64
+
65
+ raw_threads
66
+ end
67
+
68
+ str = threads.map do |thread, info|
69
+ if info[:exclusive]
70
+ lock_state = +"Exclusive"
71
+ elsif info[:sharing] > 0
72
+ lock_state = +"Sharing"
73
+ lock_state << " x#{info[:sharing]}" if info[:sharing] > 1
74
+ else
75
+ lock_state = +"No lock"
76
+ end
77
+
78
+ if info[:waiting]
79
+ lock_state << " (yielded share)"
80
+ end
81
+
82
+ msg = +"Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n"
83
+
84
+ if info[:sleeper]
85
+ msg << " Waiting in #{info[:sleeper]}"
86
+ msg << " to #{info[:purpose].to_s.inspect}" unless info[:purpose].nil?
87
+ msg << "\n"
88
+
89
+ if info[:compatible]
90
+ compat = info[:compatible].map { |c| c == false ? "share" : c.to_s.inspect }
91
+ msg << " may be pre-empted for: #{compat.join(', ')}\n"
92
+ end
93
+
94
+ blockers = threads.values.select { |binfo| blocked_by?(info, binfo, threads.values) }
95
+ msg << " blocked by: #{blockers.map { |i| i[:index] }.join(', ')}\n" if blockers.any?
96
+ end
97
+
98
+ blockees = threads.values.select { |binfo| blocked_by?(binfo, info, threads.values) }
99
+ msg << " blocking: #{blockees.map { |i| i[:index] }.join(', ')}\n" if blockees.any?
100
+
101
+ msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace]
102
+ end.join("\n\n---\n\n\n")
103
+
104
+ [200, { Rack::CONTENT_TYPE => "text/plain; charset=#{ActionDispatch::Response.default_charset}",
105
+ Rack::CONTENT_LENGTH => str.size.to_s }, [str]]
106
+ end
107
+
108
+ def blocked_by?(victim, blocker, all_threads)
109
+ return false if victim.equal?(blocker)
110
+
111
+ case victim[:sleeper]
112
+ when :start_sharing
113
+ blocker[:exclusive] ||
114
+ (!victim[:waiting] && blocker[:compatible] && !blocker[:compatible].include?(false))
115
+ when :start_exclusive
116
+ blocker[:sharing] > 0 ||
117
+ blocker[:exclusive] ||
118
+ (blocker[:compatible] && !blocker[:compatible].include?(victim[:purpose]))
119
+ when :yield_shares
120
+ blocker[:exclusive]
121
+ when :stop_exclusive
122
+ blocker[:exclusive] ||
123
+ victim[:compatible] &&
124
+ victim[:compatible].include?(blocker[:purpose]) &&
125
+ all_threads.all? { |other| !other[:compatible] || blocker.equal?(other) || other[:compatible].include?(blocker[:purpose]) }
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "pp"
6
+
7
+ require "action_view"
8
+ require "action_view/base"
9
+
10
+ module ActionDispatch
11
+ class DebugView < ActionView::Base # :nodoc:
12
+ RESCUES_TEMPLATE_PATHS = [File.expand_path("templates", __dir__)]
13
+
14
+ def initialize(assigns)
15
+ paths = RESCUES_TEMPLATE_PATHS.dup
16
+ lookup_context = ActionView::LookupContext.new(paths)
17
+ super(lookup_context, assigns, nil)
18
+ @exception_wrapper = assigns[:exception_wrapper]
19
+ end
20
+
21
+ def compiled_method_container
22
+ self.class
23
+ end
24
+
25
+ def error_highlight_available?
26
+ @exception_wrapper.error_highlight_available?
27
+ end
28
+
29
+ def debug_params(params)
30
+ clean_params = params.clone
31
+ clean_params.delete("action")
32
+ clean_params.delete("controller")
33
+
34
+ if clean_params.empty?
35
+ "None"
36
+ else
37
+ PP.pp(clean_params, +"", 200)
38
+ end
39
+ end
40
+
41
+ def debug_headers(headers)
42
+ if headers.present?
43
+ headers.inspect.gsub(",", ",\n")
44
+ else
45
+ "None"
46
+ end
47
+ end
48
+
49
+ def debug_hash(object)
50
+ object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
51
+ end
52
+
53
+ def render(*)
54
+ logger = ActionView::Base.logger
55
+
56
+ if logger && logger.respond_to?(:silence)
57
+ logger.silence { super }
58
+ else
59
+ super
60
+ end
61
+ end
62
+
63
+ def protect_against_forgery?
64
+ false
65
+ end
66
+
67
+ def params_valid?
68
+ @request.parameters
69
+ rescue ActionController::BadRequest
70
+ false
71
+ end
72
+ end
73
+ end