actionpack 4.1.7 → 4.2.11

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 (112) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +404 -451
  3. data/README.rdoc +7 -2
  4. data/lib/abstract_controller/base.rb +16 -6
  5. data/lib/abstract_controller/callbacks.rb +28 -51
  6. data/lib/abstract_controller/helpers.rb +11 -4
  7. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  8. data/lib/abstract_controller/rendering.rb +7 -1
  9. data/lib/abstract_controller/url_for.rb +1 -1
  10. data/lib/action_controller/base.rb +3 -2
  11. data/lib/action_controller/caching/fragments.rb +7 -1
  12. data/lib/action_controller/caching.rb +1 -1
  13. data/lib/action_controller/log_subscriber.rb +26 -26
  14. data/lib/action_controller/metal/conditional_get.rb +37 -12
  15. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  16. data/lib/action_controller/metal/exceptions.rb +1 -1
  17. data/lib/action_controller/metal/force_ssl.rb +1 -1
  18. data/lib/action_controller/metal/head.rb +7 -3
  19. data/lib/action_controller/metal/http_authentication.rb +20 -10
  20. data/lib/action_controller/metal/instrumentation.rb +8 -5
  21. data/lib/action_controller/metal/live.rb +57 -6
  22. data/lib/action_controller/metal/mime_responds.rb +25 -246
  23. data/lib/action_controller/metal/params_wrapper.rb +5 -5
  24. data/lib/action_controller/metal/rack_delegation.rb +1 -1
  25. data/lib/action_controller/metal/redirecting.rb +14 -8
  26. data/lib/action_controller/metal/renderers.rb +29 -11
  27. data/lib/action_controller/metal/rendering.rb +2 -6
  28. data/lib/action_controller/metal/request_forgery_protection.rb +78 -7
  29. data/lib/action_controller/metal/streaming.rb +1 -1
  30. data/lib/action_controller/metal/strong_parameters.rb +129 -14
  31. data/lib/action_controller/metal/url_for.rb +11 -12
  32. data/lib/action_controller/metal.rb +12 -11
  33. data/lib/action_controller/model_naming.rb +1 -1
  34. data/lib/action_controller/railtie.rb +4 -0
  35. data/lib/action_controller/test_case.rb +119 -75
  36. data/lib/action_controller.rb +1 -1
  37. data/lib/action_dispatch/http/cache.rb +5 -4
  38. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  39. data/lib/action_dispatch/http/headers.rb +43 -9
  40. data/lib/action_dispatch/http/mime_negotiation.rb +10 -3
  41. data/lib/action_dispatch/http/mime_type.rb +18 -4
  42. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  43. data/lib/action_dispatch/http/parameters.rb +11 -26
  44. data/lib/action_dispatch/http/request.rb +37 -11
  45. data/lib/action_dispatch/http/response.rb +74 -23
  46. data/lib/action_dispatch/http/upload.rb +9 -8
  47. data/lib/action_dispatch/http/url.rb +89 -70
  48. data/lib/action_dispatch/journey/formatter.rb +34 -18
  49. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  50. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  51. data/lib/action_dispatch/journey/gtg/transition_table.rb +20 -28
  52. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  53. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  54. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  55. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  56. data/lib/action_dispatch/journey/parser.rb +52 -60
  57. data/lib/action_dispatch/journey/parser.y +11 -10
  58. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  59. data/lib/action_dispatch/journey/route.rb +4 -19
  60. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  61. data/lib/action_dispatch/journey/router/utils.rb +1 -1
  62. data/lib/action_dispatch/journey/router.rb +53 -77
  63. data/lib/action_dispatch/journey/routes.rb +4 -0
  64. data/lib/action_dispatch/journey/scanner.rb +5 -5
  65. data/lib/action_dispatch/journey/visitors.rb +81 -92
  66. data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
  67. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  68. data/lib/action_dispatch/middleware/callbacks.rb +1 -1
  69. data/lib/action_dispatch/middleware/cookies.rb +34 -34
  70. data/lib/action_dispatch/middleware/debug_exceptions.rb +15 -4
  71. data/lib/action_dispatch/middleware/exception_wrapper.rb +50 -18
  72. data/lib/action_dispatch/middleware/flash.rb +13 -7
  73. data/lib/action_dispatch/middleware/params_parser.rb +1 -1
  74. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
  75. data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
  76. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  77. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  78. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
  79. data/lib/action_dispatch/middleware/ssl.rb +1 -1
  80. data/lib/action_dispatch/middleware/static.rb +75 -39
  81. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
  82. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +37 -9
  83. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
  84. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +2 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
  90. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
  91. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
  92. data/lib/action_dispatch/railtie.rb +2 -0
  93. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  94. data/lib/action_dispatch/routing/inspector.rb +5 -12
  95. data/lib/action_dispatch/routing/mapper.rb +414 -283
  96. data/lib/action_dispatch/routing/polymorphic_routes.rb +191 -79
  97. data/lib/action_dispatch/routing/redirection.rb +10 -12
  98. data/lib/action_dispatch/routing/route_set.rb +300 -173
  99. data/lib/action_dispatch/routing/routes_proxy.rb +5 -4
  100. data/lib/action_dispatch/routing/url_for.rb +17 -5
  101. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  102. data/lib/action_dispatch/testing/assertions/response.rb +2 -7
  103. data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
  104. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  105. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  106. data/lib/action_dispatch/testing/assertions.rb +11 -7
  107. data/lib/action_dispatch/testing/integration.rb +28 -20
  108. data/lib/action_dispatch/testing/test_request.rb +1 -1
  109. data/lib/action_dispatch/testing/test_response.rb +1 -5
  110. data/lib/action_pack/gem_version.rb +3 -3
  111. metadata +55 -13
  112. data/lib/action_controller/metal/responder.rb +0 -297
@@ -13,9 +13,9 @@ module ActionController
13
13
  end
14
14
 
15
15
  module ClassMethods
16
- # Allows you to consider additional controller-wide information when generating an etag.
16
+ # Allows you to consider additional controller-wide information when generating an ETag.
17
17
  # For example, if you serve pages tailored depending on who's logged in at the moment, you
18
- # may want to add the current user id to be part of the etag to prevent authorized displaying
18
+ # may want to add the current user id to be part of the ETag to prevent authorized displaying
19
19
  # of cached pages.
20
20
  #
21
21
  # class InvoicesController < ApplicationController
@@ -32,7 +32,7 @@ module ActionController
32
32
  end
33
33
  end
34
34
 
35
- # Sets the etag, +last_modified+, or both on the response and renders a
35
+ # Sets the +etag+, +last_modified+, or both on the response and renders a
36
36
  # <tt>304 Not Modified</tt> response if the request is already fresh.
37
37
  #
38
38
  # === Parameters:
@@ -41,6 +41,11 @@ module ActionController
41
41
  # * <tt>:last_modified</tt>.
42
42
  # * <tt>:public</tt> By default the Cache-Control header is private, set this to
43
43
  # +true+ if you want your application to be cachable by other devices (proxy caches).
44
+ # * <tt>:template</tt> By default, the template digest for the current
45
+ # controller/action is included in ETags. If the action renders a
46
+ # different template, you can include its digest instead. If the action
47
+ # doesn't render a template at all, you can pass <tt>template: false</tt>
48
+ # to skip any attempt to check for a template digest.
44
49
  #
45
50
  # === Example:
46
51
  #
@@ -49,11 +54,11 @@ module ActionController
49
54
  # fresh_when(etag: @article, last_modified: @article.created_at, public: true)
50
55
  # end
51
56
  #
52
- # This will render the show template if the request isn't sending a matching etag or
57
+ # This will render the show template if the request isn't sending a matching ETag or
53
58
  # If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
54
59
  #
55
60
  # You can also just pass a record where +last_modified+ will be set by calling
56
- # +updated_at+ and the etag by passing the object itself.
61
+ # +updated_at+ and the +etag+ by passing the object itself.
57
62
  #
58
63
  # def show
59
64
  # @article = Article.find(params[:id])
@@ -66,18 +71,24 @@ module ActionController
66
71
  # @article = Article.find(params[:id])
67
72
  # fresh_when(@article, public: true)
68
73
  # end
74
+ #
75
+ # When rendering a different template than the default controller/action
76
+ # style, you can indicate which digest to include in the ETag:
77
+ #
78
+ # before_action { fresh_when @article, template: 'widgets/show' }
79
+ #
69
80
  def fresh_when(record_or_options, additional_options = {})
70
81
  if record_or_options.is_a? Hash
71
82
  options = record_or_options
72
- options.assert_valid_keys(:etag, :last_modified, :public)
83
+ options.assert_valid_keys(:etag, :last_modified, :public, :template)
73
84
  else
74
85
  record = record_or_options
75
86
  options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options)
76
87
  end
77
88
 
78
- response.etag = combine_etags(options[:etag]) if options[:etag]
79
- response.last_modified = options[:last_modified] if options[:last_modified]
80
- response.cache_control[:public] = true if options[:public]
89
+ response.etag = combine_etags(options) if options[:etag] || options[:template]
90
+ response.last_modified = options[:last_modified] if options[:last_modified]
91
+ response.cache_control[:public] = true if options[:public]
81
92
 
82
93
  head :not_modified if request.fresh?(response)
83
94
  end
@@ -93,6 +104,11 @@ module ActionController
93
104
  # * <tt>:last_modified</tt>.
94
105
  # * <tt>:public</tt> By default the Cache-Control header is private, set this to
95
106
  # +true+ if you want your application to be cachable by other devices (proxy caches).
107
+ # * <tt>:template</tt> By default, the template digest for the current
108
+ # controller/action is included in ETags. If the action renders a
109
+ # different template, you can include its digest instead. If the action
110
+ # doesn't render a template at all, you can pass <tt>template: false</tt>
111
+ # to skip any attempt to check for a template digest.
96
112
  #
97
113
  # === Example:
98
114
  #
@@ -108,7 +124,7 @@ module ActionController
108
124
  # end
109
125
  #
110
126
  # You can also just pass a record where +last_modified+ will be set by calling
111
- # updated_at and the etag by passing the object itself.
127
+ # +updated_at+ and the +etag+ by passing the object itself.
112
128
  #
113
129
  # def show
114
130
  # @article = Article.find(params[:id])
@@ -133,6 +149,14 @@ module ActionController
133
149
  # end
134
150
  # end
135
151
  # end
152
+ #
153
+ # When rendering a different template than the default controller/action
154
+ # style, you can indicate which digest to include in the ETag:
155
+ #
156
+ # def show
157
+ # super if stale? @article, template: 'widgets/show'
158
+ # end
159
+ #
136
160
  def stale?(record_or_options, additional_options = {})
137
161
  fresh_when(record_or_options, additional_options)
138
162
  !request.fresh?(response)
@@ -168,8 +192,9 @@ module ActionController
168
192
  end
169
193
 
170
194
  private
171
- def combine_etags(etag)
172
- [ etag, *etaggers.map { |etagger| instance_exec(&etagger) }.compact ]
195
+ def combine_etags(options)
196
+ etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact
197
+ etags.unshift options[:etag]
173
198
  end
174
199
  end
175
200
  end
@@ -0,0 +1,50 @@
1
+ module ActionController
2
+ # When our views change, they should bubble up into HTTP cache freshness
3
+ # and bust browser caches. So the template digest for the current action
4
+ # is automatically included in the ETag.
5
+ #
6
+ # Enabled by default for apps that use Action View. Disable by setting
7
+ #
8
+ # config.action_controller.etag_with_template_digest = false
9
+ #
10
+ # Override the template to digest by passing +:template+ to +fresh_when+
11
+ # and +stale?+ calls. For example:
12
+ #
13
+ # # We're going to render widgets/show, not posts/show
14
+ # fresh_when @post, template: 'widgets/show'
15
+ #
16
+ # # We're not going to render a template, so omit it from the ETag.
17
+ # fresh_when @post, template: false
18
+ #
19
+ module EtagWithTemplateDigest
20
+ extend ActiveSupport::Concern
21
+
22
+ include ActionController::ConditionalGet
23
+
24
+ included do
25
+ class_attribute :etag_with_template_digest
26
+ self.etag_with_template_digest = true
27
+
28
+ ActiveSupport.on_load :action_view, yield: true do |action_view_base|
29
+ etag do |options|
30
+ determine_template_etag(options) if etag_with_template_digest
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+ def determine_template_etag(options)
37
+ if template = pick_template_for_etag(options)
38
+ lookup_and_digest_template(template)
39
+ end
40
+ end
41
+
42
+ def pick_template_for_etag(options)
43
+ options.fetch(:template) { "#{controller_name}/#{action_name}" }
44
+ end
45
+
46
+ def lookup_and_digest_template(template)
47
+ ActionView::Digestor.digest name: template, finder: lookup_context
48
+ end
49
+ end
50
+ end
@@ -25,7 +25,7 @@ module ActionController
25
25
  end
26
26
  end
27
27
 
28
- class ActionController::UrlGenerationError < RoutingError #:nodoc:
28
+ class ActionController::UrlGenerationError < ActionControllerError #:nodoc:
29
29
  end
30
30
 
31
31
  class MethodNotAllowed < ActionControllerError #:nodoc:
@@ -85,7 +85,7 @@ module ActionController
85
85
  if host_or_options.is_a?(Hash)
86
86
  options.merge!(host_or_options)
87
87
  elsif host_or_options
88
- options.merge!(:host => host_or_options)
88
+ options[:host] = host_or_options
89
89
  end
90
90
 
91
91
  secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS))
@@ -14,6 +14,8 @@ module ActionController
14
14
  # return head(:method_not_allowed) unless request.post?
15
15
  # return head(:bad_request) unless valid_request?
16
16
  # render
17
+ #
18
+ # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols.
17
19
  def head(status, options = {})
18
20
  options, status = status, nil if status.is_a?(Hash)
19
21
  status ||= options.delete(:status) || :ok
@@ -27,15 +29,17 @@ module ActionController
27
29
  self.status = status
28
30
  self.location = url_for(location) if location
29
31
 
30
- if include_content?(self._status_code)
32
+ self.response_body = ""
33
+
34
+ if include_content?(self.response_code)
31
35
  self.content_type = content_type || (Mime[formats.first] if formats)
32
36
  self.response.charset = false if self.response
33
- self.response_body = " "
34
37
  else
35
38
  headers.delete('Content-Type')
36
39
  headers.delete('Content-Length')
37
- self.response_body = ""
38
40
  end
41
+
42
+ true
39
43
  end
40
44
 
41
45
  private
@@ -1,4 +1,5 @@
1
1
  require 'base64'
2
+ require 'active_support/security_utils'
2
3
 
3
4
  module ActionController
4
5
  # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
@@ -53,10 +54,8 @@ module ActionController
53
54
  # In your integration tests, you can do something like this:
54
55
  #
55
56
  # def test_access_granted_from_xml
56
- # get(
57
- # "/notes/1.xml", nil,
58
- # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
59
- # )
57
+ # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
58
+ # get "/notes/1.xml"
60
59
  #
61
60
  # assert_equal 200, status
62
61
  # end
@@ -70,7 +69,11 @@ module ActionController
70
69
  def http_basic_authenticate_with(options = {})
71
70
  before_action(options.except(:name, :password, :realm)) do
72
71
  authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
73
- name == options[:name] && password == options[:password]
72
+ # This comparison uses & so that it doesn't short circuit and
73
+ # uses `variable_size_secure_compare` so that length information
74
+ # isn't leaked.
75
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(name, options[:name]) &
76
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(password, options[:password])
74
77
  end
75
78
  end
76
79
  end
@@ -397,6 +400,7 @@ module ActionController
397
400
  #
398
401
  # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
399
402
  module Token
403
+ TOKEN_KEY = 'token='
400
404
  TOKEN_REGEX = /^Token /
401
405
  AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
402
406
  extend self
@@ -462,16 +466,22 @@ module ActionController
462
466
  raw_params.map { |param| param.split %r/=(.+)?/ }
463
467
  end
464
468
 
465
- # This removes the `"` characters wrapping the value.
469
+ # This removes the <tt>"</tt> characters wrapping the value.
466
470
  def rewrite_param_values(array_params)
467
471
  array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' }
468
472
  end
469
473
 
470
474
  # This method takes an authorization body and splits up the key-value
471
- # pairs by the standardized `:`, `;`, or `\t` delimiters defined in
472
- # `AUTHN_PAIR_DELIMITERS`.
475
+ # pairs by the standardized <tt>:</tt>, <tt>;</tt>, or <tt>\t</tt>
476
+ # delimiters defined in +AUTHN_PAIR_DELIMITERS+.
473
477
  def raw_params(auth)
474
- auth.sub(TOKEN_REGEX, '').split(/"\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
478
+ _raw_params = auth.sub(TOKEN_REGEX, '').split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
479
+
480
+ if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
481
+ _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
482
+ end
483
+
484
+ _raw_params
475
485
  end
476
486
 
477
487
  # Encodes the given token and options into an Authorization header value.
@@ -481,7 +491,7 @@ module ActionController
481
491
  #
482
492
  # Returns String.
483
493
  def encode_credentials(token, options = {})
484
- values = ["token=#{token.to_s.inspect}"] + options.map do |key, value|
494
+ values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value|
485
495
  "#{key}=#{value.to_s.inspect}"
486
496
  end
487
497
  "Token #{values * ", "}"
@@ -21,17 +21,20 @@ module ActionController
21
21
  :action => self.action_name,
22
22
  :params => request.filtered_parameters,
23
23
  :format => request.format.try(:ref),
24
- :method => request.method,
24
+ :method => request.request_method,
25
25
  :path => (request.fullpath rescue "unknown")
26
26
  }
27
27
 
28
28
  ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
29
29
 
30
30
  ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
31
- result = super
32
- payload[:status] = response.status
33
- append_info_to_payload(payload)
34
- result
31
+ begin
32
+ result = super
33
+ payload[:status] = response.status
34
+ result
35
+ ensure
36
+ append_info_to_payload(payload)
37
+ end
35
38
  end
36
39
  end
37
40
 
@@ -102,16 +102,30 @@ module ActionController
102
102
  end
103
103
  end
104
104
 
105
- @stream.write "data: #{json}\n\n"
105
+ message = json.gsub(/\n/, "\ndata: ")
106
+ @stream.write "data: #{message}\n\n"
106
107
  end
107
108
  end
108
109
 
110
+ class ClientDisconnected < RuntimeError
111
+ end
112
+
109
113
  class Buffer < ActionDispatch::Response::Buffer #:nodoc:
110
114
  include MonitorMixin
111
115
 
116
+ # Ignore that the client has disconnected.
117
+ #
118
+ # If this value is `true`, calling `write` after the client
119
+ # disconnects will result in the written content being silently
120
+ # discarded. If this value is `false` (the default), a
121
+ # ClientDisconnected exception will be raised.
122
+ attr_accessor :ignore_disconnect
123
+
112
124
  def initialize(response)
113
125
  @error_callback = lambda { true }
114
126
  @cv = new_cond
127
+ @aborted = false
128
+ @ignore_disconnect = false
115
129
  super(response, SizedQueue.new(10))
116
130
  end
117
131
 
@@ -122,6 +136,17 @@ module ActionController
122
136
  end
123
137
 
124
138
  super
139
+
140
+ unless connected?
141
+ @buf.clear
142
+
143
+ unless @ignore_disconnect
144
+ # Raise ClientDisconnected, which is a RuntimeError (not an
145
+ # IOError), because that's more appropriate for something beyond
146
+ # the developer's control.
147
+ raise ClientDisconnected, "client disconnected"
148
+ end
149
+ end
125
150
  end
126
151
 
127
152
  def each
@@ -132,6 +157,10 @@ module ActionController
132
157
  @response.sent!
133
158
  end
134
159
 
160
+ # Write a 'close' event to the buffer; the producer/writing thread
161
+ # uses this to notify us that it's finished supplying content.
162
+ #
163
+ # See also #abort.
135
164
  def close
136
165
  synchronize do
137
166
  super
@@ -140,6 +169,26 @@ module ActionController
140
169
  end
141
170
  end
142
171
 
172
+ # Inform the producer/writing thread that the client has
173
+ # disconnected; the reading thread is no longer interested in
174
+ # anything that's being written.
175
+ #
176
+ # See also #close.
177
+ def abort
178
+ synchronize do
179
+ @aborted = true
180
+ @buf.clear
181
+ end
182
+ end
183
+
184
+ # Is the client still connected and waiting for content?
185
+ #
186
+ # The result of calling `write` when this is `false` is determined
187
+ # by `ignore_disconnect`.
188
+ def connected?
189
+ !@aborted
190
+ end
191
+
143
192
  def await_close
144
193
  synchronize do
145
194
  @cv.wait_until { @closed }
@@ -156,7 +205,7 @@ module ActionController
156
205
  end
157
206
 
158
207
  class Response < ActionDispatch::Response #:nodoc: all
159
- class Header < DelegateClass(Hash)
208
+ class Header < DelegateClass(Hash) # :nodoc:
160
209
  def initialize(response, header)
161
210
  @response = response
162
211
  super(header)
@@ -254,10 +303,12 @@ module ActionController
254
303
  logger = ActionController::Base.logger
255
304
  return unless logger
256
305
 
257
- message = "\n#{exception.class} (#{exception.message}):\n"
258
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
259
- message << " " << exception.backtrace.join("\n ")
260
- logger.fatal("#{message}\n\n")
306
+ logger.fatal do
307
+ message = "\n#{exception.class} (#{exception.message}):\n"
308
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
309
+ message << " " << exception.backtrace.join("\n ")
310
+ "#{message}\n\n"
311
+ end
261
312
  end
262
313
 
263
314
  def response_body=(body)
@@ -1,61 +1,28 @@
1
- require 'active_support/core_ext/array/extract_options'
2
1
  require 'abstract_controller/collector'
3
2
 
4
3
  module ActionController #:nodoc:
5
4
  module MimeResponds
6
5
  extend ActiveSupport::Concern
7
6
 
8
- included do
9
- class_attribute :responder, :mimes_for_respond_to
10
- self.responder = ActionController::Responder
11
- clear_respond_to
12
- end
13
-
7
+ # :stopdoc:
14
8
  module ClassMethods
15
- # Defines mime types that are rendered by default when invoking
16
- # <tt>respond_with</tt>.
17
- #
18
- # respond_to :html, :xml, :json
19
- #
20
- # Specifies that all actions in the controller respond to requests
21
- # for <tt>:html</tt>, <tt>:xml</tt> and <tt>:json</tt>.
22
- #
23
- # To specify on per-action basis, use <tt>:only</tt> and
24
- # <tt>:except</tt> with an array of actions or a single action:
25
- #
26
- # respond_to :html
27
- # respond_to :xml, :json, except: [ :edit ]
28
- #
29
- # This specifies that all actions respond to <tt>:html</tt>
30
- # and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
31
- # <tt>:json</tt>.
32
- #
33
- # respond_to :json, only: :create
34
- #
35
- # This specifies that the <tt>:create</tt> action and no other responds
36
- # to <tt>:json</tt>.
37
- def respond_to(*mimes)
38
- options = mimes.extract_options!
39
-
40
- only_actions = Array(options.delete(:only)).map(&:to_s)
41
- except_actions = Array(options.delete(:except)).map(&:to_s)
42
-
43
- new = mimes_for_respond_to.dup
44
- mimes.each do |mime|
45
- mime = mime.to_sym
46
- new[mime] = {}
47
- new[mime][:only] = only_actions unless only_actions.empty?
48
- new[mime][:except] = except_actions unless except_actions.empty?
49
- end
50
- self.mimes_for_respond_to = new.freeze
9
+ def respond_to(*)
10
+ raise NoMethodError, "The controller-level `respond_to' feature has " \
11
+ "been extracted to the `responders` gem. Add it to your Gemfile to " \
12
+ "continue using this feature:\n" \
13
+ " gem 'responders', '~> 2.0'\n" \
14
+ "Consult the Rails upgrade guide for details."
51
15
  end
16
+ end
52
17
 
53
- # Clear all mime types in <tt>respond_to</tt>.
54
- #
55
- def clear_respond_to
56
- self.mimes_for_respond_to = Hash.new.freeze
57
- end
18
+ def respond_with(*)
19
+ raise NoMethodError, "The `respond_with' feature has been extracted " \
20
+ "to the `responders` gem. Add it to your Gemfile to continue using " \
21
+ "this feature:\n" \
22
+ " gem 'responders', '~> 2.0'\n" \
23
+ "Consult the Rails upgrade guide for details."
58
24
  end
25
+ # :startdoc:
59
26
 
60
27
  # Without web-service support, an action which collects the data for displaying a list of people
61
28
  # might look something like this:
@@ -169,18 +136,6 @@ module ActionController #:nodoc:
169
136
  #
170
137
  # render json: @people
171
138
  #
172
- # Since this is a common pattern, you can use the class method respond_to
173
- # with the respond_with method to have the same results:
174
- #
175
- # class PeopleController < ApplicationController
176
- # respond_to :html, :xml, :json
177
- #
178
- # def index
179
- # @people = Person.all
180
- # respond_with(@people)
181
- # end
182
- # end
183
- #
184
139
  # Formats can have different variants.
185
140
  #
186
141
  # The request variant is a specialization of the request format, like <tt>:tablet</tt>,
@@ -217,7 +172,7 @@ module ActionController #:nodoc:
217
172
  # format.html.phone { redirect_to progress_path }
218
173
  # format.html.none { render "trash" }
219
174
  # end
220
- #
175
+ #
221
176
  # Variants also support common `any`/`all` block that formats have.
222
177
  #
223
178
  # It works for both inline:
@@ -248,194 +203,18 @@ module ActionController #:nodoc:
248
203
  # format.html.phone # this gets rendered
249
204
  # end
250
205
  #
251
- # Be sure to check the documentation of +respond_with+ and
252
- # <tt>ActionController::MimeResponds.respond_to</tt> for more examples.
253
- def respond_to(*mimes, &block)
206
+ # Be sure to check the documentation of <tt>ActionController::MimeResponds.respond_to</tt>
207
+ # for more examples.
208
+ def respond_to(*mimes)
254
209
  raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
255
210
 
256
- if collector = retrieve_collector_from_mimes(mimes, &block)
257
- response = collector.response
258
- response ? response.call : render({})
259
- end
260
- end
261
-
262
- # For a given controller action, respond_with generates an appropriate
263
- # response based on the mime-type requested by the client.
264
- #
265
- # If the method is called with just a resource, as in this example -
266
- #
267
- # class PeopleController < ApplicationController
268
- # respond_to :html, :xml, :json
269
- #
270
- # def index
271
- # @people = Person.all
272
- # respond_with @people
273
- # end
274
- # end
275
- #
276
- # then the mime-type of the response is typically selected based on the
277
- # request's Accept header and the set of available formats declared
278
- # by previous calls to the controller's class method +respond_to+. Alternatively
279
- # the mime-type can be selected by explicitly setting <tt>request.format</tt> in
280
- # the controller.
281
- #
282
- # If an acceptable format is not identified, the application returns a
283
- # '406 - not acceptable' status. Otherwise, the default response is to render
284
- # a template named after the current action and the selected format,
285
- # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior
286
- # depends on the selected format:
287
- #
288
- # * for an html response - if the request method is +get+, an exception
289
- # is raised but for other requests such as +post+ the response
290
- # depends on whether the resource has any validation errors (i.e.
291
- # assuming that an attempt has been made to save the resource,
292
- # e.g. by a +create+ action) -
293
- # 1. If there are no errors, i.e. the resource
294
- # was saved successfully, the response +redirect+'s to the resource
295
- # i.e. its +show+ action.
296
- # 2. If there are validation errors, the response
297
- # renders a default action, which is <tt>:new</tt> for a
298
- # +post+ request or <tt>:edit</tt> for +patch+ or +put+.
299
- # Thus an example like this -
300
- #
301
- # respond_to :html, :xml
302
- #
303
- # def create
304
- # @user = User.new(params[:user])
305
- # flash[:notice] = 'User was successfully created.' if @user.save
306
- # respond_with(@user)
307
- # end
308
- #
309
- # is equivalent, in the absence of <tt>create.html.erb</tt>, to -
310
- #
311
- # def create
312
- # @user = User.new(params[:user])
313
- # respond_to do |format|
314
- # if @user.save
315
- # flash[:notice] = 'User was successfully created.'
316
- # format.html { redirect_to(@user) }
317
- # format.xml { render xml: @user }
318
- # else
319
- # format.html { render action: "new" }
320
- # format.xml { render xml: @user }
321
- # end
322
- # end
323
- # end
324
- #
325
- # * for a javascript request - if the template isn't found, an exception is
326
- # raised.
327
- # * for other requests - i.e. data formats such as xml, json, csv etc, if
328
- # the resource passed to +respond_with+ responds to <code>to_<format></code>,
329
- # the method attempts to render the resource in the requested format
330
- # directly, e.g. for an xml request, the response is equivalent to calling
331
- # <code>render xml: resource</code>.
332
- #
333
- # === Nested resources
334
- #
335
- # As outlined above, the +resources+ argument passed to +respond_with+
336
- # can play two roles. It can be used to generate the redirect url
337
- # for successful html requests (e.g. for +create+ actions when
338
- # no template exists), while for formats other than html and javascript
339
- # it is the object that gets rendered, by being converted directly to the
340
- # required format (again assuming no template exists).
341
- #
342
- # For redirecting successful html requests, +respond_with+ also supports
343
- # the use of nested resources, which are supplied in the same way as
344
- # in <code>form_for</code> and <code>polymorphic_url</code>. For example -
345
- #
346
- # def create
347
- # @project = Project.find(params[:project_id])
348
- # @task = @project.comments.build(params[:task])
349
- # flash[:notice] = 'Task was successfully created.' if @task.save
350
- # respond_with(@project, @task)
351
- # end
352
- #
353
- # This would cause +respond_with+ to redirect to <code>project_task_url</code>
354
- # instead of <code>task_url</code>. For request formats other than html or
355
- # javascript, if multiple resources are passed in this way, it is the last
356
- # one specified that is rendered.
357
- #
358
- # === Customizing response behavior
359
- #
360
- # Like +respond_to+, +respond_with+ may also be called with a block that
361
- # can be used to overwrite any of the default responses, e.g. -
362
- #
363
- # def create
364
- # @user = User.new(params[:user])
365
- # flash[:notice] = "User was successfully created." if @user.save
366
- #
367
- # respond_with(@user) do |format|
368
- # format.html { render }
369
- # end
370
- # end
371
- #
372
- # The argument passed to the block is an ActionController::MimeResponds::Collector
373
- # object which stores the responses for the formats defined within the
374
- # block. Note that formats with responses defined explicitly in this way
375
- # do not have to first be declared using the class method +respond_to+.
376
- #
377
- # Also, a hash passed to +respond_with+ immediately after the specified
378
- # resource(s) is interpreted as a set of options relevant to all
379
- # formats. Any option accepted by +render+ can be used, e.g.
380
- # respond_with @people, status: 200
381
- # However, note that these options are ignored after an unsuccessful attempt
382
- # to save a resource, e.g. when automatically rendering <tt>:new</tt>
383
- # after a post request.
384
- #
385
- # Two additional options are relevant specifically to +respond_with+ -
386
- # 1. <tt>:location</tt> - overwrites the default redirect location used after
387
- # a successful html +post+ request.
388
- # 2. <tt>:action</tt> - overwrites the default render action used after an
389
- # unsuccessful html +post+ request.
390
- def respond_with(*resources, &block)
391
- if self.class.mimes_for_respond_to.empty?
392
- raise "In order to use respond_with, first you need to declare the " \
393
- "formats your controller responds to in the class level."
394
- end
395
-
396
- if collector = retrieve_collector_from_mimes(&block)
397
- options = resources.size == 1 ? {} : resources.extract_options!
398
- options = options.clone
399
- options[:default_response] = collector.response
400
- (options.delete(:responder) || self.class.responder).call(self, resources, options)
401
- end
402
- end
403
-
404
- protected
405
-
406
- # Collect mimes declared in the class method respond_to valid for the
407
- # current action.
408
- def collect_mimes_from_class_level #:nodoc:
409
- action = action_name.to_s
410
-
411
- self.class.mimes_for_respond_to.keys.select do |mime|
412
- config = self.class.mimes_for_respond_to[mime]
413
-
414
- if config[:except]
415
- !config[:except].include?(action)
416
- elsif config[:only]
417
- config[:only].include?(action)
418
- else
419
- true
420
- end
421
- end
422
- end
423
-
424
- # Returns a Collector object containing the appropriate mime-type response
425
- # for the current request, based on the available responses defined by a block.
426
- # In typical usage this is the block passed to +respond_with+ or +respond_to+.
427
- #
428
- # Sends :not_acceptable to the client and returns nil if no suitable format
429
- # is available.
430
- def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
431
- mimes ||= collect_mimes_from_class_level
432
211
  collector = Collector.new(mimes, request.variant)
433
- block.call(collector) if block_given?
434
- format = collector.negotiate_format(request)
212
+ yield collector if block_given?
435
213
 
436
- if format
214
+ if format = collector.negotiate_format(request)
437
215
  _process_format(format)
438
- collector
216
+ response = collector.response
217
+ response ? response.call : render({})
439
218
  else
440
219
  raise ActionController::UnknownFormat
441
220
  end
@@ -444,8 +223,8 @@ module ActionController #:nodoc:
444
223
  # A container for responses available from the current controller for
445
224
  # requests for different mime-types sent to a particular action.
446
225
  #
447
- # The public controller methods +respond_with+ and +respond_to+ may be called
448
- # with a block that is used to define responses to different mime-types, e.g.
226
+ # The public controller methods +respond_to+ may be called with a block
227
+ # that is used to define responses to different mime-types, e.g.
449
228
  # for +respond_to+ :
450
229
  #
451
230
  # respond_to do |format|