Skip to content
This repository
Browse code

Merge branch 'conditional-get'

  • Loading branch information...
commit 45b79d933cd2433b30ae98e7dadc4ae060e170c9 2 parents 992fda1 + 08b0cf0
Jeremy Kemper authored August 12, 2008
10  actionpack/CHANGELOG
@@ -7,8 +7,14 @@
7 7
 * Update Prototype to 1.6.0.2 #599 [Patrick Joyce]
8 8
 
9 9
 * Conditional GET utility methods.  [Jeremy Kemper]
10  
-  * etag!([:admin, post, current_user]) sets the ETag response header and returns head(:not_modified) if it matches the If-None-Match request header.
11  
-  * last_modified!(post.updated_at) sets Last-Modified and returns head(:not_modified) if it's no later than If-Modified-Since.
  10
+    response.last_modified = @post.updated_at
  11
+    response.etag = [:admin, @post, current_user]
  12
+
  13
+    if request.fresh?(response)
  14
+      head :not_modified
  15
+    else
  16
+      # render ...
  17
+    end
12 18
 
13 19
 * All 2xx requests are considered successful [Josh Peek]
14 20
 
45  actionpack/lib/action_controller/cgi_process.rb
@@ -43,7 +43,7 @@ class SessionFixationAttempt < StandardError #:nodoc:
43 43
       :session_path     => "/",             # available to all paths in app
44 44
       :session_key      => "_session_id",
45 45
       :cookie_only      => true
46  
-    } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
  46
+    }
47 47
 
48 48
     def initialize(cgi, session_options = {})
49 49
       @cgi = cgi
@@ -61,53 +61,14 @@ def query_string
61 61
       end
62 62
     end
63 63
 
64  
-    # The request body is an IO input stream. If the RAW_POST_DATA environment
65  
-    # variable is already set, wrap it in a StringIO.
66  
-    def body
67  
-      if raw_post = env['RAW_POST_DATA']
68  
-        raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
69  
-        StringIO.new(raw_post)
70  
-      else
71  
-        @cgi.stdinput
72  
-      end
73  
-    end
74  
-
75  
-    def query_parameters
76  
-      @query_parameters ||= self.class.parse_query_parameters(query_string)
77  
-    end
78  
-
79  
-    def request_parameters
80  
-      @request_parameters ||= parse_formatted_request_parameters
  64
+    def body_stream #:nodoc:
  65
+      @cgi.stdinput
81 66
     end
82 67
 
83 68
     def cookies
84 69
       @cgi.cookies.freeze
85 70
     end
86 71
 
87  
-    def host_with_port_without_standard_port_handling
88  
-      if forwarded = env["HTTP_X_FORWARDED_HOST"]
89  
-        forwarded.split(/,\s?/).last
90  
-      elsif http_host = env['HTTP_HOST']
91  
-        http_host
92  
-      elsif server_name = env['SERVER_NAME']
93  
-        server_name
94  
-      else
95  
-        "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
96  
-      end
97  
-    end
98  
-
99  
-    def host
100  
-      host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
101  
-    end
102  
-
103  
-    def port
104  
-      if host_with_port_without_standard_port_handling =~ /:(\d+)$/
105  
-        $1.to_i
106  
-      else
107  
-        standard_port
108  
-      end
109  
-    end
110  
-
111 72
     def session
112 73
       unless defined?(@session)
113 74
         if @session_options == false
30  actionpack/lib/action_controller/headers.rb
... ...
@@ -1,31 +1,33 @@
  1
+require 'active_support/memoizable'
  2
+
1 3
 module ActionController
2 4
   module Http
3 5
     class Headers < ::Hash
4  
-      
5  
-      def initialize(constructor = {})
6  
-         if constructor.is_a?(Hash)
  6
+      extend ActiveSupport::Memoizable
  7
+
  8
+      def initialize(*args)
  9
+         if args.size == 1 && args[0].is_a?(Hash)
7 10
            super()
8  
-           update(constructor)
  11
+           update(args[0])
9 12
          else
10  
-           super(constructor)
  13
+           super
11 14
          end
12 15
        end
13  
-      
  16
+
14 17
       def [](header_name)
15 18
         if include?(header_name)
16  
-          super 
  19
+          super
17 20
         else
18  
-          super(normalize_header(header_name))
  21
+          super(env_name(header_name))
19 22
         end
20 23
       end
21  
-      
22  
-      
  24
+
23 25
       private
24  
-        # Takes an HTTP header name and returns it in the 
25  
-        # format 
26  
-        def normalize_header(header_name)
  26
+        # Converts a HTTP header name to an environment variable name.
  27
+        def env_name(header_name)
27 28
           "HTTP_#{header_name.upcase.gsub(/-/, '_')}"
28 29
         end
  30
+        memoize :env_name
29 31
     end
30 32
   end
31  
-end
  33
+end
52  actionpack/lib/action_controller/rack_process.rb
@@ -3,7 +3,7 @@
3 3
 
4 4
 module ActionController #:nodoc:
5 5
   class RackRequest < AbstractRequest #:nodoc:
6  
-    attr_accessor :env, :session_options
  6
+    attr_accessor :session_options
7 7
     attr_reader :cgi
8 8
 
9 9
     class SessionFixationAttempt < StandardError #:nodoc:
@@ -15,7 +15,7 @@ class SessionFixationAttempt < StandardError #:nodoc:
15 15
       :session_path     => "/",             # available to all paths in app
16 16
       :session_key      => "_session_id",
17 17
       :cookie_only      => true
18  
-    } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
  18
+    }
19 19
 
20 20
     def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
21 21
       @session_options = session_options
@@ -30,35 +30,21 @@ def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
30 30
         SERVER_NAME SERVER_PROTOCOL
31 31
 
32 32
         HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
33  
-        HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
  33
+        HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
34 34
         HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
35 35
       define_method(env.sub(/^HTTP_/n, '').downcase) do
36 36
         @env[env]
37 37
       end
38 38
     end
39 39
 
40  
-    # The request body is an IO input stream. If the RAW_POST_DATA environment
41  
-    # variable is already set, wrap it in a StringIO.
42  
-    def body
43  
-      if raw_post = env['RAW_POST_DATA']
44  
-        StringIO.new(raw_post)
45  
-      else
46  
-        @env['rack.input']
47  
-      end
  40
+    def body_stream #:nodoc:
  41
+      @env['rack.input']
48 42
     end
49 43
 
50 44
     def key?(key)
51 45
       @env.key?(key)
52 46
     end
53 47
 
54  
-    def query_parameters
55  
-      @query_parameters ||= self.class.parse_query_parameters(query_string)
56  
-    end
57  
-
58  
-    def request_parameters
59  
-      @request_parameters ||= parse_formatted_request_parameters
60  
-    end
61  
-
62 48
     def cookies
63 49
       return {} unless @env["HTTP_COOKIE"]
64 50
 
@@ -70,34 +56,6 @@ def cookies
70 56
       @env["rack.request.cookie_hash"]
71 57
     end
72 58
 
73  
-    def host_with_port_without_standard_port_handling
74  
-      if forwarded = @env["HTTP_X_FORWARDED_HOST"]
75  
-        forwarded.split(/,\s?/).last
76  
-      elsif http_host = @env['HTTP_HOST']
77  
-        http_host
78  
-      elsif server_name = @env['SERVER_NAME']
79  
-        server_name
80  
-      else
81  
-        "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
82  
-      end
83  
-    end
84  
-
85  
-    def host
86  
-      host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
87  
-    end
88  
-
89  
-    def port
90  
-      if host_with_port_without_standard_port_handling =~ /:(\d+)$/
91  
-        $1.to_i
92  
-      else
93  
-        standard_port
94  
-      end
95  
-    end
96  
-
97  
-    def remote_addr
98  
-      @env['REMOTE_ADDR']
99  
-    end
100  
-
101 59
     def server_port
102 60
       @env['SERVER_PORT'].to_i
103 61
     end
159  actionpack/lib/action_controller/request.rb
@@ -2,18 +2,22 @@
2 2
 require 'stringio'
3 3
 require 'strscan'
4 4
 
5  
-module ActionController
6  
-  # HTTP methods which are accepted by default.
7  
-  ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
  5
+require 'active_support/memoizable'
8 6
 
  7
+module ActionController
9 8
   # CgiRequest and TestRequest provide concrete implementations.
10 9
   class AbstractRequest
  10
+    extend ActiveSupport::Memoizable
  11
+
11 12
     def self.relative_url_root=(*args)
12 13
       ActiveSupport::Deprecation.warn(
13 14
         "ActionController::AbstractRequest.relative_url_root= has been renamed." +
14 15
         "You can now set it with config.action_controller.relative_url_root=", caller)
15 16
     end
16 17
 
  18
+    HTTP_METHODS = %w(get head put post delete options)
  19
+    HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
  20
+
17 21
     # The hash of environment variables for this request,
18 22
     # such as { 'RAILS_ENV' => 'production' }.
19 23
     attr_reader :env
@@ -21,15 +25,12 @@ def self.relative_url_root=(*args)
21 25
     # The true HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
22 26
     # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
23 27
     def request_method
24  
-      @request_method ||= begin
25  
-        method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase
26  
-        if ACCEPTED_HTTP_METHODS.include?(method)
27  
-          method.to_sym
28  
-        else
29  
-          raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}"
30  
-        end
31  
-      end
  28
+      method = @env['REQUEST_METHOD']
  29
+      method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank?
  30
+
  31
+      HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
32 32
     end
  33
+    memoize :request_method
33 34
 
34 35
     # The HTTP request method as a lowercase symbol, such as <tt>:get</tt>.
35 36
     # Note, HEAD is returned as <tt>:get</tt> since the two are functionally
@@ -67,33 +68,59 @@ def head?
67 68
     # Provides access to the request's HTTP headers, for example:
68 69
     #  request.headers["Content-Type"] # => "text/plain"
69 70
     def headers
70  
-      @headers ||= ActionController::Http::Headers.new(@env)
  71
+      ActionController::Http::Headers.new(@env)
71 72
     end
  73
+    memoize :headers
72 74
 
73 75
     def content_length
74  
-      @content_length ||= env['CONTENT_LENGTH'].to_i
  76
+      @env['CONTENT_LENGTH'].to_i
75 77
     end
  78
+    memoize :content_length
76 79
 
77 80
     # The MIME type of the HTTP request, such as Mime::XML.
78 81
     #
79 82
     # For backward compatibility, the post format is extracted from the
80 83
     # X-Post-Data-Format HTTP header if present.
81 84
     def content_type
82  
-      @content_type ||= Mime::Type.lookup(content_type_without_parameters)
  85
+      Mime::Type.lookup(content_type_without_parameters)
83 86
     end
  87
+    memoize :content_type
84 88
 
85 89
     # Returns the accepted MIME type for the request
86 90
     def accepts
87  
-      @accepts ||=
88  
-        begin
89  
-          header = @env['HTTP_ACCEPT'].to_s.strip
  91
+      header = @env['HTTP_ACCEPT'].to_s.strip
90 92
 
91  
-          if header.empty?
92  
-            [content_type, Mime::ALL].compact
93  
-          else
94  
-            Mime::Type.parse(header)
95  
-          end
96  
-        end
  93
+      if header.empty?
  94
+        [content_type, Mime::ALL].compact
  95
+      else
  96
+        Mime::Type.parse(header)
  97
+      end
  98
+    end
  99
+    memoize :accepts
  100
+
  101
+    def if_modified_since
  102
+      if since = env['HTTP_IF_MODIFIED_SINCE']
  103
+        Time.rfc2822(since)
  104
+      end
  105
+    end
  106
+    memoize :if_modified_since
  107
+
  108
+    def if_none_match
  109
+      env['HTTP_IF_NONE_MATCH']
  110
+    end
  111
+
  112
+    def not_modified?(modified_at)
  113
+      if_modified_since && modified_at && if_modified_since >= modified_at
  114
+    end
  115
+
  116
+    def etag_matches?(etag)
  117
+      if_none_match && if_none_match == etag
  118
+    end
  119
+
  120
+    # Check response freshness (Last-Modified and ETag) against request
  121
+    # If-Modified-Since and If-None-Match conditions.
  122
+    def fresh?(response)
  123
+      not_modified?(response.last_modified) || etag_matches?(response.etag)
97 124
     end
98 125
 
99 126
     # Returns the Mime type for the format used in the request.
@@ -102,7 +129,7 @@ def accepts
102 129
     #   GET /posts/5.xhtml | request.format => Mime::HTML
103 130
     #   GET /posts/5       | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
104 131
     def format
105  
-      @format ||= begin
  132
+      @format ||=
106 133
         if parameters[:format]
107 134
           Mime::Type.lookup_by_extension(parameters[:format])
108 135
         elsif ActionController::Base.use_accept_header
@@ -112,7 +139,6 @@ def format
112 139
         else
113 140
           Mime::Type.lookup_by_extension("html")
114 141
         end
115  
-      end
116 142
     end
117 143
 
118 144
 
@@ -200,42 +226,62 @@ def remote_ip
200 226
 
201 227
       @env['REMOTE_ADDR']
202 228
     end
  229
+    memoize :remote_ip
203 230
 
204 231
     # Returns the lowercase name of the HTTP server software.
205 232
     def server_software
206 233
       (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
207 234
     end
  235
+    memoize :server_software
208 236
 
209 237
 
210 238
     # Returns the complete URL used for this request
211 239
     def url
212 240
       protocol + host_with_port + request_uri
213 241
     end
  242
+    memoize :url
214 243
 
215 244
     # Return 'https://' if this is an SSL request and 'http://' otherwise.
216 245
     def protocol
217 246
       ssl? ? 'https://' : 'http://'
218 247
     end
  248
+    memoize :protocol
219 249
 
220 250
     # Is this an SSL request?
221 251
     def ssl?
222 252
       @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
223 253
     end
224 254
 
  255
+    def raw_host_with_port
  256
+      if forwarded = env["HTTP_X_FORWARDED_HOST"]
  257
+        forwarded.split(/,\s?/).last
  258
+      else
  259
+        env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
  260
+      end
  261
+    end
  262
+
225 263
     # Returns the host for this request, such as example.com.
226 264
     def host
  265
+      raw_host_with_port.sub(/:\d+$/, '')
227 266
     end
  267
+    memoize :host
228 268
 
229 269
     # Returns a host:port string for this request, such as example.com or
230 270
     # example.com:8080.
231 271
     def host_with_port
232  
-      @host_with_port ||= host + port_string
  272
+      "#{host}#{port_string}"
233 273
     end
  274
+    memoize :host_with_port
234 275
 
235 276
     # Returns the port number of this request as an integer.
236 277
     def port
237  
-      @port_as_int ||= @env['SERVER_PORT'].to_i
  278
+      if raw_host_with_port =~ /:(\d+)$/
  279
+        $1.to_i
  280
+      else
  281
+        standard_port
  282
+      end
238 283
     end
  284
+    memoize :port
239 285
 
240 286
     # Returns the standard port number for this request's protocol
241 287
     def standard_port
@@ -248,7 +294,7 @@ def standard_port
248 294
     # Returns a port suffix like ":8080" if the port number of this request
249 295
     # is not the default HTTP port 80 or HTTPS port 443.
250 296
     def port_string
251  
-      (port == standard_port) ? '' : ":#{port}"
  297
+      port == standard_port ? '' : ":#{port}"
252 298
     end
253 299
 
254 300
     # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
@@ -276,6 +322,7 @@ def query_string
276 322
         @env['QUERY_STRING'] || ''
277 323
       end
278 324
     end
  325
+    memoize :query_string
279 326
 
280 327
     # Return the request URI, accounting for server idiosyncrasies.
281 328
     # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
@@ -285,21 +332,23 @@ def request_uri
285 332
         (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
286 333
       else
287 334
         # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
288  
-        script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
289  
-        uri = @env['PATH_INFO']
290  
-        uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
291  
-        unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
292  
-          uri << '?' << env_qs
  335
+        uri = @env['PATH_INFO'].to_s
  336
+
  337
+        if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
  338
+          uri = uri.sub(/#{script_filename}\//, '')
293 339
         end
294 340
 
295  
-        if uri.nil?
  341
+        env_qs = @env['QUERY_STRING'].to_s
  342
+        uri += "?#{env_qs}" unless env_qs.empty?
  343
+
  344
+        if uri.blank?
296 345
           @env.delete('REQUEST_URI')
297  
-          uri
298 346
         else
299 347
           @env['REQUEST_URI'] = uri
300 348
         end
301 349
       end
302 350
     end
  351
+    memoize :request_uri
303 352
 
304 353
     # Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
305 354
     def path
@@ -309,6 +358,7 @@ def path
309 358
       path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '')
310 359
       path || ''
311 360
     end
  361
+    memoize :path
312 362
 
313 363
     # Read the request body. This is useful for web services that need to
314 364
     # work with raw requests directly.
@@ -345,19 +395,41 @@ def path_parameters
345 395
       @path_parameters ||= {}
346 396
     end
347 397
 
  398
+    # The request body is an IO input stream. If the RAW_POST_DATA environment
  399
+    # variable is already set, wrap it in a StringIO.
  400
+    def body
  401
+      if raw_post = env['RAW_POST_DATA']
  402
+        raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
  403
+        StringIO.new(raw_post)
  404
+      else
  405
+        body_stream
  406
+      end
  407
+    end
348 408
 
349  
-    #--
350  
-    # Must be implemented in the concrete request
351  
-    #++
  409
+    def remote_addr
  410
+      @env['REMOTE_ADDR']
  411
+    end
352 412
 
353  
-    # The request body is an IO input stream.
354  
-    def body
  413
+    def referrer
  414
+      @env['HTTP_REFERER']
  415
+    end
  416
+    alias referer referrer
  417
+
  418
+
  419
+    def query_parameters
  420
+      @query_parameters ||= self.class.parse_query_parameters(query_string)
355 421
     end
356 422
 
357  
-    def query_parameters #:nodoc:
  423
+    def request_parameters
  424
+      @request_parameters ||= parse_formatted_request_parameters
358 425
     end
359 426
 
360  
-    def request_parameters #:nodoc:
  427
+
  428
+    #--
  429
+    # Must be implemented in the concrete request
  430
+    #++
  431
+
  432
+    def body_stream #:nodoc:
361 433
     end
362 434
 
363 435
     def cookies #:nodoc:
@@ -384,8 +456,9 @@ def content_type_with_parameters
384 456
 
385 457
       # The raw content type string with its parameters stripped off.
386 458
       def content_type_without_parameters
387  
-        @content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters)
  459
+        self.class.extract_content_type_without_parameters(content_type_with_parameters)
388 460
       end
  461
+      memoize :content_type_without_parameters
389 462
 
390 463
     private
391 464
       def content_type_from_legacy_post_data_format_header
54  actionpack/lib/action_controller/response.rb
@@ -37,12 +37,20 @@ class AbstractResponse
37 37
     attr_accessor :body
38 38
     # The headers of the response, as a Hash. It maps header names to header values.
39 39
     attr_accessor :headers
40  
-    attr_accessor :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
  40
+    attr_accessor :session, :cookies, :assigns, :template, :layout
  41
+    attr_accessor :redirected_to, :redirected_to_method_params
41 42
 
42 43
     def initialize
43 44
       @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
44 45
     end
45 46
 
  47
+    def status; headers['Status'] end
  48
+    def status=(status) headers['Status'] = status end
  49
+
  50
+    def location; headers['Location'] end
  51
+    def location=(url) headers['Location'] = url end
  52
+
  53
+
46 54
     # Sets the HTTP response's content MIME type. For example, in the controller
47 55
     # you could write this:
48 56
     #
@@ -70,35 +78,29 @@ def charset
70 78
       charset.blank? ? nil : charset.strip.split("=")[1]
71 79
     end
72 80
 
73  
-    def redirect(to_url, response_status)
74  
-      self.headers["Status"] = response_status
75  
-      self.headers["Location"] = to_url
  81
+    def last_modified
  82
+      Time.rfc2822(headers['Last-Modified'])
  83
+    end
76 84
 
77  
-      self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
  85
+    def last_modified=(utc_time)
  86
+      headers['Last-Modified'] = utc_time.httpdate
78 87
     end
79 88
 
80  
-    def prepare!
81  
-      handle_conditional_get!
82  
-      convert_content_type!
83  
-      set_content_length!
  89
+    def etag; headers['ETag'] end
  90
+    def etag=(etag)
  91
+      headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
84 92
     end
85 93
 
86  
-    # Sets the Last-Modified response header. Returns whether it's older than
87  
-    # the If-Modified-Since request header.
88  
-    def last_modified!(utc_time)
89  
-      headers['Last-Modified'] ||= utc_time.httpdate
90  
-      if request && since = request.headers['HTTP_IF_MODIFIED_SINCE']
91  
-        utc_time <= Time.rfc2822(since)
92  
-      end
  94
+    def redirect(url, status)
  95
+      self.status = status
  96
+      self.location = url
  97
+      self.body = "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
93 98
     end
94 99
 
95  
-    # Sets the ETag response header. Returns whether it matches the
96  
-    # If-None-Match request header.
97  
-    def etag!(tag)
98  
-      headers['ETag'] ||= %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(tag))}")
99  
-      if request && request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
100  
-        true
101  
-      end
  100
+    def prepare!
  101
+      handle_conditional_get!
  102
+      convert_content_type!
  103
+      set_content_length!
102 104
     end
103 105
 
104 106
     private
@@ -106,15 +108,15 @@ def handle_conditional_get!
106 108
         if nonempty_ok_response?
107 109
           set_conditional_cache_control!
108 110
 
109  
-          if etag!(body)
110  
-            headers['Status'] = '304 Not Modified'
  111
+          self.etag ||= body
  112
+          if request && request.etag_matches?(etag)
  113
+            self.status = '304 Not Modified'
111 114
             self.body = ''
112 115
           end
113 116
         end
114 117
       end
115 118
 
116 119
       def nonempty_ok_response?
117  
-        status = headers['Status']
118 120
         ok = !status || status[0..2] == '200'
119 121
         ok && body.is_a?(String) && !body.empty?
120 122
       end
38  actionpack/lib/action_controller/test_process.rb
@@ -23,7 +23,7 @@ def process_with_test(*args)
23 23
 
24 24
   class TestRequest < AbstractRequest #:nodoc:
25 25
     attr_accessor :cookies, :session_options
26  
-    attr_accessor :query_parameters, :request_parameters, :path, :session, :env
  26
+    attr_accessor :query_parameters, :request_parameters, :path, :session
27 27
     attr_accessor :host, :user_agent
28 28
 
29 29
     def initialize(query_parameters = nil, request_parameters = nil, session = nil)
@@ -42,7 +42,7 @@ def reset_session
42 42
     end
43 43
 
44 44
     # Wraps raw_post in a StringIO.
45  
-    def body
  45
+    def body_stream #:nodoc:
46 46
       StringIO.new(raw_post)
47 47
     end
48 48
 
@@ -54,7 +54,7 @@ def raw_post
54 54
 
55 55
     def port=(number)
56 56
       @env["SERVER_PORT"] = number.to_i
57  
-      @port_as_int = nil
  57
+      port(true)
58 58
     end
59 59
 
60 60
     def action=(action_name)
@@ -68,6 +68,8 @@ def set_REQUEST_URI(value)
68 68
       @env["REQUEST_URI"] = value
69 69
       @request_uri = nil
70 70
       @path = nil
  71
+      request_uri(true)
  72
+      path(true)
71 73
     end
72 74
 
73 75
     def request_uri=(uri)
@@ -77,21 +79,26 @@ def request_uri=(uri)
77 79
 
78 80
     def accept=(mime_types)
79 81
       @env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
  82
+      accepts(true)
80 83
     end
81 84
 
82  
-    def remote_addr=(addr)
83  
-      @env['REMOTE_ADDR'] = addr
  85
+    def if_modified_since=(last_modified)
  86
+      @env["HTTP_IF_MODIFIED_SINCE"] = last_modified
84 87
     end
85 88
 
86  
-    def remote_addr
87  
-      @env['REMOTE_ADDR']
  89
+    def if_none_match=(etag)
  90
+      @env["HTTP_IF_NONE_MATCH"] = etag
  91
+    end
  92
+
  93
+    def remote_addr=(addr)
  94
+      @env['REMOTE_ADDR'] = addr
88 95
     end
89 96
 
90  
-    def request_uri
  97
+    def request_uri(*args)
91 98
       @request_uri || super
92 99
     end
93 100
 
94  
-    def path
  101
+    def path(*args)
95 102
       @path || super
96 103
     end
97 104
 
@@ -120,10 +127,6 @@ def recycle!
120 127
       self.query_parameters   = {}
121 128
       self.path_parameters    = {}
122 129
       @request_method, @accepts, @content_type = nil, nil, nil
123  
-    end    
124  
-
125  
-    def referer
126  
-      @env["HTTP_REFERER"]
127 130
     end
128 131
 
129 132
     private
@@ -448,10 +451,13 @@ def find_all_tag(conditions)
448 451
     end
449 452
 
450 453
     def method_missing(selector, *args)
451  
-      return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
452  
-      return super
  454
+      if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
  455
+        @controller.send(selector, *args)
  456
+      else
  457
+        super
  458
+      end
453 459
     end
454  
-    
  460
+
455 461
     # Shortcut for <tt>ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type)</tt>:
456 462
     #
457 463
     #   post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
6  actionpack/lib/action_view/renderable.rb
@@ -31,10 +31,10 @@ def render(view, local_assigns = {})
31 31
 
32 32
       view.send(:evaluate_assigns)
33 33
       view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type)
34  
-      view.send(:execute, method(local_assigns), local_assigns)
  34
+      view.send(:execute, method_name(local_assigns), local_assigns)
35 35
     end
36 36
 
37  
-    def method(local_assigns)
  37
+    def method_name(local_assigns)
38 38
       if local_assigns && local_assigns.any?
39 39
         local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
40 40
       end
@@ -44,7 +44,7 @@ def method(local_assigns)
44 44
     private
45 45
       # Compile and evaluate the template's code (if necessary)
46 46
       def compile(local_assigns)
47  
-        render_symbol = method(local_assigns)
  47
+        render_symbol = method_name(local_assigns)
48 48
 
49 49
         @@mutex.synchronize do
50 50
           if recompile?(render_symbol)
2  actionpack/test/controller/caching_test.rb
@@ -109,7 +109,7 @@ def test_should_cache_with_trailing_slash_on_url
109 109
 
110 110
   uses_mocha("should_cache_ok_at_custom_path") do
111 111
     def test_should_cache_ok_at_custom_path
112  
-      @request.expects(:path).returns("/index.html")
  112
+      @request.stubs(:path).returns("/index.html")
113 113
       get :ok
114 114
       assert_response :ok
115 115
       assert File.exist?("#{FILE_STORE_PATH}/index.html")
2  actionpack/test/controller/cgi_test.rb
@@ -75,7 +75,7 @@ def test_http_host
75 75
     assert_equal "rubyonrails.org:8080", @request.host_with_port
76 76
 
77 77
     @request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
78  
-    assert_equal "www.secondhost.org", @request.host
  78
+    assert_equal "www.secondhost.org", @request.host(true)
79 79
   end
80 80
 
81 81
   def test_http_host_with_default_port_overrides_server_port
8  actionpack/test/controller/content_type_test.rb
@@ -128,23 +128,23 @@ def teardown
128 128
 
129 129
 
130 130
   def test_render_default_content_types_for_respond_to
131  
-    @request.env["HTTP_ACCEPT"] = Mime::HTML.to_s
  131
+    @request.accept = Mime::HTML.to_s
132 132
     get :render_default_content_types_for_respond_to
133 133
     assert_equal Mime::HTML, @response.content_type
134 134
 
135  
-    @request.env["HTTP_ACCEPT"] = Mime::JS.to_s
  135
+    @request.accept = Mime::JS.to_s
136 136
     get :render_default_content_types_for_respond_to
137 137
     assert_equal Mime::JS, @response.content_type
138 138
   end
139 139
 
140 140
   def test_render_default_content_types_for_respond_to_with_template
141  
-    @request.env["HTTP_ACCEPT"] = Mime::XML.to_s
  141
+    @request.accept = Mime::XML.to_s
142 142
     get :render_default_content_types_for_respond_to
143 143
     assert_equal Mime::XML, @response.content_type
144 144
   end
145 145
 
146 146
   def test_render_default_content_types_for_respond_to_with_overwrite
147  
-    @request.env["HTTP_ACCEPT"] = Mime::RSS.to_s
  147
+    @request.accept = Mime::RSS.to_s
148 148
     get :render_default_content_types_for_respond_to
149 149
     assert_equal Mime::XML, @response.content_type
150 150
   end
62  actionpack/test/controller/mime_responds_test.rb
@@ -177,7 +177,7 @@ def teardown
177 177
   end
178 178
 
179 179
   def test_html
180  
-    @request.env["HTTP_ACCEPT"] = "text/html"
  180
+    @request.accept = "text/html"
181 181
     get :js_or_html
182 182
     assert_equal 'HTML', @response.body
183 183
 
@@ -189,7 +189,7 @@ def test_html
189 189
   end
190 190
 
191 191
   def test_all
192  
-    @request.env["HTTP_ACCEPT"] = "*/*"
  192
+    @request.accept = "*/*"
193 193
     get :js_or_html
194 194
     assert_equal 'HTML', @response.body # js is not part of all
195 195
 
@@ -201,13 +201,13 @@ def test_all
201 201
   end
202 202
 
203 203
   def test_xml
204  
-    @request.env["HTTP_ACCEPT"] = "application/xml"
  204
+    @request.accept = "application/xml"
205 205
     get :html_xml_or_rss
206 206
     assert_equal 'XML', @response.body
207 207
   end
208 208
 
209 209
   def test_js_or_html
210  
-    @request.env["HTTP_ACCEPT"] = "text/javascript, text/html"
  210
+    @request.accept = "text/javascript, text/html"
211 211
     get :js_or_html
212 212
     assert_equal 'JS', @response.body
213 213
 
@@ -232,7 +232,7 @@ def test_json_or_yaml
232 232
       'JSON' => %w(application/json text/x-json)
233 233
     }.each do |body, content_types|
234 234
       content_types.each do |content_type|
235  
-        @request.env['HTTP_ACCEPT'] = content_type
  235
+        @request.accept = content_type
236 236
         get :json_or_yaml
237 237
         assert_equal body, @response.body
238 238
       end
@@ -240,7 +240,7 @@ def test_json_or_yaml
240 240
   end
241 241
 
242 242
   def test_js_or_anything
243  
-    @request.env["HTTP_ACCEPT"] = "text/javascript, */*"
  243
+    @request.accept = "text/javascript, */*"
244 244
     get :js_or_html
245 245
     assert_equal 'JS', @response.body
246 246
 
@@ -252,34 +252,34 @@ def test_js_or_anything
252 252
   end
253 253
 
254 254
   def test_using_defaults
255  
-    @request.env["HTTP_ACCEPT"] = "*/*"
  255
+    @request.accept = "*/*"
256 256
     get :using_defaults
257 257
     assert_equal "text/html", @response.content_type
258 258
     assert_equal 'Hello world!', @response.body
259 259
 
260  
-    @request.env["HTTP_ACCEPT"] = "text/javascript"
  260
+    @request.accept = "text/javascript"
261 261
     get :using_defaults
262 262
     assert_equal "text/javascript", @response.content_type
263 263
     assert_equal '$("body").visualEffect("highlight");', @response.body
264 264
 
265  
-    @request.env["HTTP_ACCEPT"] = "application/xml"
  265
+    @request.accept = "application/xml"
266 266
     get :using_defaults
267 267
     assert_equal "application/xml", @response.content_type
268 268
     assert_equal "<p>Hello world!</p>\n", @response.body
269 269
   end
270 270
 
271 271
   def test_using_defaults_with_type_list
272  
-    @request.env["HTTP_ACCEPT"] = "*/*"
  272
+    @request.accept = "*/*"
273 273
     get :using_defaults_with_type_list
274 274
     assert_equal "text/html", @response.content_type
275 275
     assert_equal 'Hello world!', @response.body
276 276
 
277  
-    @request.env["HTTP_ACCEPT"] = "text/javascript"
  277
+    @request.accept = "text/javascript"
278 278
     get :using_defaults_with_type_list
279 279
     assert_equal "text/javascript", @response.content_type
280 280
     assert_equal '$("body").visualEffect("highlight");', @response.body
281 281
 
282  
-    @request.env["HTTP_ACCEPT"] = "application/xml"
  282
+    @request.accept = "application/xml"
283 283
     get :using_defaults_with_type_list
284 284
     assert_equal "application/xml", @response.content_type
285 285
     assert_equal "<p>Hello world!</p>\n", @response.body
@@ -298,55 +298,55 @@ def test_with_rss_content_type
298 298
   end
299 299
 
300 300
   def test_synonyms
301  
-    @request.env["HTTP_ACCEPT"] = "application/javascript"
  301
+    @request.accept = "application/javascript"
302 302
     get :js_or_html
303 303
     assert_equal 'JS', @response.body
304 304
 
305  
-    @request.env["HTTP_ACCEPT"] = "application/x-xml"
  305
+    @request.accept = "application/x-xml"
306 306
     get :html_xml_or_rss
307 307
     assert_equal "XML", @response.body
308 308
   end
309 309
 
310 310
   def test_custom_types
311  
-    @request.env["HTTP_ACCEPT"] = "application/crazy-xml"
  311
+    @request.accept = "application/crazy-xml"
312 312
     get :custom_type_handling
313 313
     assert_equal "application/crazy-xml", @response.content_type
314 314
     assert_equal 'Crazy XML', @response.body
315 315
 
316  
-    @request.env["HTTP_ACCEPT"] = "text/html"
  316
+    @request.accept = "text/html"
317 317
     get :custom_type_handling
318 318
     assert_equal "text/html", @response.content_type
319 319
     assert_equal 'HTML', @response.body
320 320
   end
321 321
 
322 322
   def test_xhtml_alias
323  
-    @request.env["HTTP_ACCEPT"] = "application/xhtml+xml,application/xml"
  323
+    @request.accept = "application/xhtml+xml,application/xml"
324 324
     get :html_or_xml
325 325
     assert_equal 'HTML', @response.body
326 326
   end
327 327
 
328 328
   def test_firefox_simulation
329  
-    @request.env["HTTP_ACCEPT"] = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
  329
+    @request.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
330 330
     get :html_or_xml
331 331
     assert_equal 'HTML', @response.body
332 332
   end
333 333
 
334 334
   def test_handle_any
335  
-    @request.env["HTTP_ACCEPT"] = "*/*"
  335
+    @request.accept = "*/*"
336 336
     get :handle_any
337 337
     assert_equal 'HTML', @response.body
338 338
 
339  
-    @request.env["HTTP_ACCEPT"] = "text/javascript"
  339
+    @request.accept = "text/javascript"
340 340
     get :handle_any
341 341
     assert_equal 'Either JS or XML', @response.body
342 342
 
343  
-    @request.env["HTTP_ACCEPT"] = "text/xml"
  343
+    @request.accept = "text/xml"
344 344
     get :handle_any
345 345
     assert_equal 'Either JS or XML', @response.body
346 346
   end
347 347
 
348 348
   def test_handle_any_any
349  
-    @request.env["HTTP_ACCEPT"] = "*/*"
  349
+    @request.accept = "*/*"
350 350
     get :handle_any_any
351 351
     assert_equal 'HTML', @response.body
352 352
   end
@@ -357,31 +357,31 @@ def test_handle_any_any_parameter_format
357 357
   end
358 358
 
359 359
   def test_handle_any_any_explicit_html
360  
-    @request.env["HTTP_ACCEPT"] = "text/html"
  360
+    @request.accept = "text/html"
361 361
     get :handle_any_any
362 362
     assert_equal 'HTML', @response.body
363 363
   end
364 364
 
365 365
   def test_handle_any_any_javascript
366  
-    @request.env["HTTP_ACCEPT"] = "text/javascript"
  366
+    @request.accept = "text/javascript"
367 367
     get :handle_any_any
368 368
     assert_equal 'Whatever you ask for, I got it', @response.body
369 369
   end
370 370
 
371 371
   def test_handle_any_any_xml
372  
-    @request.env["HTTP_ACCEPT"] = "text/xml"
  372
+    @request.accept = "text/xml"
373 373
     get :handle_any_any
374 374
     assert_equal 'Whatever you ask for, I got it', @response.body
375 375
   end
376 376
 
377 377
   def test_rjs_type_skips_layout
378  
-    @request.env["HTTP_ACCEPT"] = "text/javascript"
  378
+    @request.accept = "text/javascript"
379 379
     get :all_types_with_layout
380 380
     assert_equal 'RJS for all_types_with_layout', @response.body
381 381
   end
382 382
 
383 383
   def test_html_type_with_layout
384  
-    @request.env["HTTP_ACCEPT"] = "text/html"
  384
+    @request.accept = "text/html"
385 385
     get :all_types_with_layout
386 386
     assert_equal '<html><div id="html">HTML for all_types_with_layout</div></html>', @response.body
387 387
   end
@@ -460,7 +460,7 @@ def test_format_with_custom_response_type
460 460
   end
461 461
 
462 462
   def test_format_with_custom_response_type_and_request_headers
463  
-    @request.env["HTTP_ACCEPT"] = "text/iphone"
  463
+    @request.accept = "text/iphone"
464 464
     get :iphone_with_html_response_type
465 465
     assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
466 466
     assert_equal "text/html", @response.content_type
@@ -470,7 +470,7 @@ def test_format_with_custom_response_type_and_request_headers_with_only_one_layo
470 470
     get :iphone_with_html_response_type_without_layout
471 471
     assert_equal '<html><div id="html_missing">Hello future from Firefox!</div></html>', @response.body
472 472
 
473  
-    @request.env["HTTP_ACCEPT"] = "text/iphone"
  473
+    @request.accept = "text/iphone"
474 474
     assert_raises(ActionView::MissingTemplate) { get :iphone_with_html_response_type_without_layout }
475 475
   end
476 476
 end
@@ -522,7 +522,7 @@ def test_missing_layout_renders_properly
522 522
     get :index
523 523
     assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body
524 524
 
525  
-    @request.env["HTTP_ACCEPT"] = "text/iphone"
  525
+    @request.accept = "text/iphone"
526 526
     get :index
527 527
     assert_equal 'Hello iPhone', @response.body
528 528
   end
@@ -533,7 +533,7 @@ def test_format_with_inherited_layouts
533 533
     get :index
534 534
     assert_equal 'Super Firefox', @response.body
535 535
 
536  
-    @request.env["HTTP_ACCEPT"] = "text/iphone"
  536
+    @request.accept = "text/iphone"
537 537
     get :index
538 538
     assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body
539 539
   end
23  actionpack/test/controller/rack_test.rb
@@ -64,58 +64,61 @@ def set_content_data(data)
64 64
 
65 65
 class RackRequestTest < BaseRackTest
66 66
   def test_proxy_request
67  
-    assert_equal 'glu.ttono.us', @request.host_with_port
  67
+    assert_equal 'glu.ttono.us', @request.host_with_port(true)
68 68
   end
69 69
 
70 70
   def test_http_host
71 71
     @env.delete "HTTP_X_FORWARDED_HOST"
72 72
     @env['HTTP_HOST'] = "rubyonrails.org:8080"
73  
-    assert_equal "rubyonrails.org:8080", @request.host_with_port
  73
+    assert_equal "rubyonrails.org", @request.host(true)
  74
+    assert_equal "rubyonrails.org:8080", @request.host_with_port(true)
74 75
 
75 76
     @env['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
76  
-    assert_equal "www.secondhost.org", @request.host
  77
+    assert_equal "www.secondhost.org", @request.host(true)
77 78
   end
78 79
 
79 80
   def test_http_host_with_default_port_overrides_server_port
80 81
     @env.delete "HTTP_X_FORWARDED_HOST"
81 82
     @env['HTTP_HOST'] = "rubyonrails.org"