diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2008-08-07 23:43:12 -0700 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2008-08-07 23:43:12 -0700 |
commit | b7529ed1cc7cfd8df5fd1b069e2881d39d3d984c (patch) | |
tree | ef69ba48ba47e42981101aab20aebae1d111c684 /actionpack/lib/action_controller/request.rb | |
parent | e43d1c226d09afe49b25f5e3a351c4c10371933a (diff) | |
download | rails-b7529ed1cc7cfd8df5fd1b069e2881d39d3d984c.tar.gz rails-b7529ed1cc7cfd8df5fd1b069e2881d39d3d984c.tar.bz2 rails-b7529ed1cc7cfd8df5fd1b069e2881d39d3d984c.zip |
Simplifying usage of ETags and Last-Modified and conditional GET requests
Diffstat (limited to 'actionpack/lib/action_controller/request.rb')
-rw-r--r-- | actionpack/lib/action_controller/request.rb | 144 |
1 files changed, 108 insertions, 36 deletions
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index c55788a531..3c1521d8b1 100644 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -2,18 +2,22 @@ require 'tempfile' require 'stringio' require 'strscan' -module ActionController - # HTTP methods which are accepted by default. - ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options )) +require 'active_support/memoizable' +module ActionController # CgiRequest and TestRequest provide concrete implementations. class AbstractRequest + extend ActiveSupport::Memoizable + def self.relative_url_root=(*args) ActiveSupport::Deprecation.warn( "ActionController::AbstractRequest.relative_url_root= has been renamed." + "You can now set it with config.action_controller.relative_url_root=", caller) end + HTTP_METHODS = %w(get head put post delete options) + HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h } + # The hash of environment variables for this request, # such as { 'RAILS_ENV' => 'production' }. attr_reader :env @@ -21,15 +25,12 @@ module ActionController # The true HTTP request method as a lowercase symbol, such as <tt>:get</tt>. # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS. def request_method - @request_method ||= begin - method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase - if ACCEPTED_HTTP_METHODS.include?(method) - method.to_sym - else - raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}" - end - end + method = @env['REQUEST_METHOD'] + method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank? + + HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}") end + memoize :request_method # The HTTP request method as a lowercase symbol, such as <tt>:get</tt>. # Note, HEAD is returned as <tt>:get</tt> since the two are functionally @@ -67,33 +68,59 @@ module ActionController # Provides access to the request's HTTP headers, for example: # request.headers["Content-Type"] # => "text/plain" def headers - @headers ||= ActionController::Http::Headers.new(@env) + ActionController::Http::Headers.new(@env) end + memoize :headers def content_length - @content_length ||= env['CONTENT_LENGTH'].to_i + @env['CONTENT_LENGTH'].to_i end + memoize :content_length # The MIME type of the HTTP request, such as Mime::XML. # # For backward compatibility, the post format is extracted from the # X-Post-Data-Format HTTP header if present. def content_type - @content_type ||= Mime::Type.lookup(content_type_without_parameters) + Mime::Type.lookup(content_type_without_parameters) end + memoize :content_type # Returns the accepted MIME type for the request def accepts - @accepts ||= - begin - header = @env['HTTP_ACCEPT'].to_s.strip + header = @env['HTTP_ACCEPT'].to_s.strip - if header.empty? - [content_type, Mime::ALL].compact - else - Mime::Type.parse(header) - end - end + if header.empty? + [content_type, Mime::ALL].compact + else + Mime::Type.parse(header) + end + end + memoize :accepts + + def if_modified_since + if since = env['HTTP_IF_MODIFIED_SINCE'] + Time.rfc2822(since) + end + end + memoize :if_modified_since + + def if_none_match + env['HTTP_IF_NONE_MATCH'] + end + + def not_modified?(modified_at) + if_modified_since && modified_at && if_modified_since >= modified_at + end + + def etag_matches?(etag) + if_none_match && if_none_match == etag + end + + # Check response freshness (Last-Modified and ETag) against request + # If-Modified-Since and If-None-Match conditions. + def fresh?(response) + not_modified?(response.last_modified) || etag_matches?(response.etag) end # Returns the Mime type for the format used in the request. @@ -102,7 +129,7 @@ module ActionController # GET /posts/5.xhtml | request.format => Mime::HTML # 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> def format - @format ||= begin + @format ||= if parameters[:format] Mime::Type.lookup_by_extension(parameters[:format]) elsif ActionController::Base.use_accept_header @@ -112,7 +139,6 @@ module ActionController else Mime::Type.lookup_by_extension("html") end - end end @@ -200,42 +226,63 @@ EOM @env['REMOTE_ADDR'] end + memoize :remote_ip # Returns the lowercase name of the HTTP server software. def server_software (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil end + memoize :server_software # Returns the complete URL used for this request def url protocol + host_with_port + request_uri end + memoize :url # Return 'https://' if this is an SSL request and 'http://' otherwise. def protocol ssl? ? 'https://' : 'http://' end + memoize :protocol # Is this an SSL request? def ssl? @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https' end + def host_with_port_without_standard_port_handling + if forwarded = env["HTTP_X_FORWARDED_HOST"] + forwarded.split(/,\s?/).last + else + env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}" + end + end + memoize :host_with_port_without_standard_port_handling + # Returns the host for this request, such as example.com. def host + host_with_port_without_standard_port_handling.sub(/:\d+\Z/, '') end + memoize :host # Returns a host:port string for this request, such as example.com or # example.com:8080. def host_with_port - @host_with_port ||= host + port_string + "#{host}#{port_string}" end + memoize :host_with_port # Returns the port number of this request as an integer. def port - @port_as_int ||= @env['SERVER_PORT'].to_i + if host_with_port_without_standard_port_handling =~ /:(\d+)$/ + $1.to_i + else + standard_port + end end + memoize :port # Returns the standard port number for this request's protocol def standard_port @@ -248,7 +295,7 @@ EOM # Returns a port suffix like ":8080" if the port number of this request # is not the default HTTP port 80 or HTTPS port 443. def port_string - (port == standard_port) ? '' : ":#{port}" + ":#{port}" unless port == standard_port end # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify @@ -276,6 +323,7 @@ EOM @env['QUERY_STRING'] || '' end end + memoize :query_string # Return the request URI, accounting for server idiosyncrasies. # WEBrick includes the full URL. IIS leaves REQUEST_URI blank. @@ -300,6 +348,7 @@ EOM end end end + memoize :request_uri # Returns the interpreted path to requested resource after all the installation directory of this application was taken into account def path @@ -345,19 +394,41 @@ EOM @path_parameters ||= {} end + # The request body is an IO input stream. If the RAW_POST_DATA environment + # variable is already set, wrap it in a StringIO. + def body + if raw_post = env['RAW_POST_DATA'] + raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding) + StringIO.new(raw_post) + else + body_stream + end + end - #-- - # Must be implemented in the concrete request - #++ + def remote_addr + @env['REMOTE_ADDR'] + end - # The request body is an IO input stream. - def body + def referrer + @env['HTTP_REFERER'] + end + alias referer referrer + + + def query_parameters + @query_parameters ||= self.class.parse_query_parameters(query_string) end - def query_parameters #:nodoc: + def request_parameters + @request_parameters ||= parse_formatted_request_parameters end - def request_parameters #:nodoc: + + #-- + # Must be implemented in the concrete request + #++ + + def body_stream #:nodoc: end def cookies #:nodoc: @@ -384,8 +455,9 @@ EOM # The raw content type string with its parameters stripped off. def content_type_without_parameters - @content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters) + self.class.extract_content_type_without_parameters(content_type_with_parameters) end + memoize :content_type_without_parameters private def content_type_from_legacy_post_data_format_header |