request.rb 9.2 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
module ActionController
2 3
  # Subclassing AbstractRequest makes these methods available to the request objects used in production and testing,
  # CgiRequest and TestRequest
D
Initial  
David Heinemeier Hansson 已提交
4
  class AbstractRequest
5
    cattr_accessor :relative_url_root
6
    remove_method :relative_url_root
7

8 9 10 11
    # Returns the hash of environment variables for this request,
    # such as { 'RAILS_ENV' => 'production' }.
    attr_reader :env

D
Initial  
David Heinemeier Hansson 已提交
12 13
    # Returns both GET and POST parameters in a single hash.
    def parameters
14
      @parameters ||= request_parameters.update(query_parameters).update(path_parameters).with_indifferent_access
D
Initial  
David Heinemeier Hansson 已提交
15 16
    end

17 18 19
    # Returns the HTTP request method as a lowercase symbol (:get, for example). Note, HEAD is returned as :get
    # since the two are supposedly to be functionaly equivilent for all purposes except that HEAD won't return a response
    # body (which Rails also takes care of elsewhere).
D
Initial  
David Heinemeier Hansson 已提交
20
    def method
21 22
      @request_method ||= (!parameters[:_method].blank? && @env['REQUEST_METHOD'] == 'POST') ?
        parameters[:_method].to_s.downcase.to_sym :
23
        @env['REQUEST_METHOD'].downcase.to_sym
24 25
      
      @request_method == :head ? :get : @request_method
D
Initial  
David Heinemeier Hansson 已提交
26 27
    end

28
    # Is this a GET (or HEAD) request?  Equivalent to request.method == :get
D
Initial  
David Heinemeier Hansson 已提交
29 30 31 32
    def get?
      method == :get
    end

33
    # Is this a POST request?  Equivalent to request.method == :post
D
Initial  
David Heinemeier Hansson 已提交
34 35 36 37
    def post?
      method == :post
    end

38
    # Is this a PUT request?  Equivalent to request.method == :put
D
Initial  
David Heinemeier Hansson 已提交
39 40 41 42
    def put?
      method == :put
    end

43
    # Is this a DELETE request?  Equivalent to request.method == :delete
D
Initial  
David Heinemeier Hansson 已提交
44 45 46 47
    def delete?
      method == :delete
    end

48 49
    # Is this a HEAD request?  HEAD is mapped as :get for request.method, so here we ask the 
    # REQUEST_METHOD header directly. Thus, for head, both get? and head? will return true.
50
    def head?
51
      @env['REQUEST_METHOD'].downcase.to_sym == :head
52
    end
53

54 55 56 57
    def headers
      @env
    end

58 59
    # Determine whether the body of a HTTP call is URL-encoded (default)
    # or matches one of the registered param_parsers. 
60 61 62
    #
    # For backward compatibility, the post format is extracted from the
    # X-Post-Data-Format HTTP header if present.
63
    def content_type
64 65 66 67 68 69 70 71 72 73 74
      @content_type ||=
        begin
          content_type = @env['CONTENT_TYPE'].to_s.downcase
          
          if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
            case x_post_format.to_s.downcase
            when 'yaml'
              content_type = 'application/x-yaml'
            when 'xml'
              content_type = 'application/xml'
            end
75
          end
76 77 78
          
          Mime::Type.lookup(content_type)
        end
79 80
    end

81
    # Returns the accepted MIME type for the request
82
    def accepts
83 84
      @accepts ||=
        if @env['HTTP_ACCEPT'].to_s.strip.empty?
85
          [ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
86 87 88
        else
          Mime::Type.parse(@env['HTTP_ACCEPT'])
        end
89
    end
90

91 92 93 94 95 96 97 98 99 100
    # Returns the Mime type for the format used in the request. If there is no format available, the first of the 
    # accept types will be used. Examples:
    #
    #   GET /posts/5.xml   | request.format => Mime::XML
    #   GET /posts/5.xhtml | request.format => Mime::HTML
    #   GET /posts/5       | request.format => request.accepts.first (usually Mime::HTML for browsers)
    def format
      parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
    end

101 102 103
    # Returns true if the request's "X-Requested-With" header contains
    # "XMLHttpRequest". (The Prototype Javascript library sends this header with
    # every Ajax request.)
104
    def xml_http_request?
105
      not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
106 107 108
    end
    alias xhr? :xml_http_request?

D
Initial  
David Heinemeier Hansson 已提交
109 110 111 112 113 114 115
    # Determine originating IP address.  REMOTE_ADDR is the standard
    # but will fail if the user is behind a proxy.  HTTP_CLIENT_IP and/or
    # HTTP_X_FORWARDED_FOR are set by proxies so check for these before
    # falling back to REMOTE_ADDR.  HTTP_X_FORWARDED_FOR may be a comma-
    # delimited list in the case of multiple chained proxies; the first is
    # the originating IP.
    def remote_ip
116
      return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP'
117

118 119
      if @env.include? 'HTTP_X_FORWARDED_FOR' then
        remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip|
120
            ip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
121 122 123
        end

        return remote_ips.first.strip unless remote_ips.empty?
D
Initial  
David Heinemeier Hansson 已提交
124
      end
125

126
      @env['REMOTE_ADDR']
D
Initial  
David Heinemeier Hansson 已提交
127 128
    end

129 130 131
    # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
    # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
    def domain(tld_length = 1)
132
      return nil if !/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.match(host).nil? or host.nil?
133

134
      host.split('.').last(1 + tld_length).join('.')
135 136 137 138 139 140
    end

    # Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org".
    # You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
    # in "www.rubyonrails.co.uk".
    def subdomains(tld_length = 1)
141
      return [] unless host
142
      parts = host.split('.')
143
      parts[0..-(tld_length+2)]
144 145
    end

146 147 148
    # Receive the raw post data.
    # This is useful for services such as REST, XMLRPC and SOAP
    # which communicate over HTTP POST but don't use the traditional parameter format.
149
    def raw_post
150
      @env['RAW_POST_DATA']
151
    end
152

153 154
    # Return the request URI, accounting for server idiosyncracies.
    # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
D
Initial  
David Heinemeier Hansson 已提交
155
    def request_uri
156
      if uri = @env['REQUEST_URI']
157 158 159 160
        # Remove domain, which webrick puts into the request_uri.
        (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
      else
        # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
161 162
        script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
        uri = @env['PATH_INFO']
163
        uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
164
        unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
165 166
          uri << '?' << env_qs
        end
167
        @env['REQUEST_URI'] = uri
168
      end
169
    end
D
Initial  
David Heinemeier Hansson 已提交
170

171
    # Return 'https://' if this is an SSL request and 'http://' otherwise.
D
Initial  
David Heinemeier Hansson 已提交
172
    def protocol
173
      ssl? ? 'https://' : 'http://'
D
Initial  
David Heinemeier Hansson 已提交
174 175
    end

176
    # Is this an SSL request?
177
    def ssl?
178
      @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
179
    end
180

181
    # Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
D
Initial  
David Heinemeier Hansson 已提交
182
    def path
183
      path = (uri = request_uri) ? uri.split('?').first : ''
184

185
      # Cut off the path to the installation directory if given
186 187
      path.sub!(%r/^#{relative_url_root}/, '')
      path || ''      
188
    end
189
    
190
    # Returns the path minus the web server relative installation directory.
191 192 193
    # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
    # It can be automatically extracted for Apache setups. If the server is not
    # Apache, this method returns an empty string.
194
    def relative_url_root
195 196 197 198 199 200 201 202
      @@relative_url_root ||= case
        when @env["RAILS_RELATIVE_URL_ROOT"]
          @env["RAILS_RELATIVE_URL_ROOT"]
        when server_software == 'apache'
          @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
        else
          ''
      end
D
Initial  
David Heinemeier Hansson 已提交
203 204
    end

205
    # Returns the port number of this request as an integer.
D
Initial  
David Heinemeier Hansson 已提交
206
    def port
207
      @port_as_int ||= @env['SERVER_PORT'].to_i
D
Initial  
David Heinemeier Hansson 已提交
208
    end
209

210 211 212 213 214 215 216
    # Returns the standard port number for this request's protocol
    def standard_port
      case protocol
        when 'https://' then 443
        else 80
      end
    end
D
Initial  
David Heinemeier Hansson 已提交
217

218 219
    # Returns a port suffix like ":8080" if the port number of this request
    # is not the default HTTP port 80 or HTTPS port 443.
220
    def port_string
221
      (port == standard_port) ? '' : ":#{port}"
222 223
    end

224 225
    # Returns a host:port string for this request, such as example.com or
    # example.com:8080.
D
Initial  
David Heinemeier Hansson 已提交
226
    def host_with_port
227
      host + port_string
D
Initial  
David Heinemeier Hansson 已提交
228
    end
229

230
    def path_parameters=(parameters) #:nodoc:
231
      @path_parameters = parameters
232 233
      @symbolized_path_parameters = @parameters = nil
    end
234

235 236
    # The same as <tt>path_parameters</tt> with explicitly symbolized keys 
    def symbolized_path_parameters 
237
      @symbolized_path_parameters ||= path_parameters.symbolize_keys
238
    end
D
Initial  
David Heinemeier Hansson 已提交
239

240 241 242 243 244
    # Returns a hash with the parameters used to form the path of the request 
    #
    # Example: 
    #
    #   {:action => 'my_action', :controller => 'my_controller'}
245 246 247
    def path_parameters
      @path_parameters ||= {}
    end
248 249

    # Returns the lowercase name of the HTTP server software.
250
    def server_software
251
      (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
252
    end
253

D
Initial  
David Heinemeier Hansson 已提交
254 255 256
    #--
    # Must be implemented in the concrete request
    #++
257
    def query_parameters #:nodoc:
D
Initial  
David Heinemeier Hansson 已提交
258 259
    end

260
    def request_parameters #:nodoc:
D
Initial  
David Heinemeier Hansson 已提交
261 262
    end

263 264
    # Returns the host for this request, such as example.com.
    def host
D
Initial  
David Heinemeier Hansson 已提交
265 266
    end

267
    def cookies #:nodoc:
D
Initial  
David Heinemeier Hansson 已提交
268 269
    end

270
    def session #:nodoc:
D
Initial  
David Heinemeier Hansson 已提交
271 272
    end

273 274 275 276
    def session=(session) #:nodoc:
      @session = session
    end

277
    def reset_session #:nodoc:
278
    end
D
Initial  
David Heinemeier Hansson 已提交
279 280
  end
end