actionpack 4.2.11.1 → 6.1.3.2

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