class Rackful::Request

Subclass of {Rack::Request}, augmented for Rackful requests.

This class mixes in module ‘StatusCodes` for convenience, as explained in the {StatusCodes StatusCodes documentation}.

Public Class Methods

new(*args) click to toggle source
Calls superclass method
# File lib/rackful/request.rb, line 18
def initialize *args
  super( *args )
end

Public Instance Methods

accept() click to toggle source

Hash of acceptable media types and their qualities.

This method parses the HTTP/1.1 ‘Accept:` header. If no acceptable media types are provided, an empty Hash is returned. @return [Hash{media_type => quality}] @deprecated Use {#q_values} instead

# File lib/rackful/request.rb, line 178
def accept
  env['rackful.accept'] ||= begin
    Hash[
      env['HTTP_ACCEPT'].to_s.split(',').collect do
        |entry|
        type, *options = entry.delete(' ').split(';')
        quality = 1
        options.each { |e|
          quality = e[2..-1].to_f if e.start_with? 'q='
        }
        [type, quality]
      end
    ]
  rescue
    {}
  end
end
assert_if_headers() click to toggle source

Assert all If-* request headers. @return [void] @raise [HTTP304NotModified, HTTP400BadRequest, HTTP404NotFound, HTTP412PreconditionFailed]

with the following meanings:

-   `304 Not Modified`
-   `400 Bad Request` Couldn't parse one or more <tt>If-*</tt> headers, or a
    weak validator comparison was requested for methods other than `GET` or
    `HEAD`.
-   `404 Not Found`
-   `412 Precondition Failed`

@see tools.ietf.org/html/rfc2616#section-13.3.3 RFC2616, section 13.3.3

for details about weak and strong validator comparison.

@todo Implement support for the ‘If-Range:` header.

# File lib/rackful/request.rb, line 89
def assert_if_headers
  #raise HTTP501NotImplemented, 'If-Range: request header is not supported.' \
  #  if env.key? 'HTTP_IF_RANGE'
  begin
    empty = resource.empty?
  rescue HTTP404NotFound => e
    empty = true
  end
  etag =
    if ! empty && resource.respond_to?(:get_etag)
      resource.get_etag
    else
      nil
    end
  last_modified =
    if ! empty && resource.respond_to?(:get_last_modified)
      resource.get_last_modified
    else
      nil
    end
  cond = {
    :match => self.if_match,
    :none_match => self.if_none_match,
    :modified_since => self.if_modified_since,
    :unmodified_since => self.if_unmodified_since
  }
  allow_weak = ['GET', 'HEAD'].include? self.request_method
  if empty
    if cond[:match]
      raise HTTP412PreconditionFailed, 'If-Match'
    elsif cond[:unmodified_since]
      raise HTTP412PreconditionFailed, 'If-Unmodified-Since'
    elsif cond[:modified_since]
      raise HTTP404NotFound
    end
  else
    if cond[:none_match] && self.validate_etag( etag, cond[:none_match] )
      if allow_weak
        raise HTTP304NotModified
      else
        raise HTTP412PreconditionFailed, 'If-None-Match'
      end
    elsif cond[:match] && ! self.validate_etag( etag, cond[:match] )
      raise HTTP412PreconditionFailed, 'If-Match'
    elsif cond[:unmodified_since]
      if ! last_modified || cond[:unmodified_since] < last_modified[0]
        raise HTTP412PreconditionFailed, 'If-Unmodified-Since'
      elsif last_modified && ! last_modified[1] && ! allow_weak &&
            cond[:unmodified_since] == last_modified[0]
        raise HTTP412PreconditionFailed, 'If-Unmodified-Since'
      end
    elsif cond[:modified_since]
      if ! last_modified || cond[:modified_since] >= last_modified[0]
        raise HTTP304NotModified
      elsif last_modified && ! last_modified[1] && !allow_weak &&
            cond[:modified_since] == last_modified[0]
        raise HTTP412PreconditionFailed, 'If-Modified-Since'
      end
    end
  end
end
canonical_uri() click to toggle source

Similar to the HTTP/1.1 ‘Content-Location:` header. Contains the canonical url of the requested resource, which may differ from {#url}.

If parameter full_path is provided, than this is used instead of the current request’s full path (which is the path plus optional query string). @return [URI::Generic]

# File lib/rackful/request.rb, line 58
def canonical_uri
  env['rackful.canonical_uri'] || URI( self.url ).normalize
end
if_match(none = false) click to toggle source

@!method if_match() Parses the HTTP/1.1 ‘If-Match:` header. @return [nil, Array<String>] @see tools.ietf.org/html/rfc2616#section-14.24 RFC2616, section 14.24 @see if_none_match

# File lib/rackful/request.rb, line 202
def if_match none = false
  header = env["HTTP_IF_#{ none ? 'NONE_' : '' }MATCH"]
  return nil unless header
  envkey = "rackful.if_#{ none ? 'none_' : '' }match"
  if %r{\A\s*\*\s*\z} === header
    return [ '*' ]
  elsif %r{\A(\s*(W/)?"([^"\\]|\\.)*"\s*,)+\z}m === ( header + ',' )
    return header.scan( %r{(?:W/)?"(?:[^"\\]|\\.)*"}m )
  end
  raise HTTP400BadRequest, "Couldn't parse If-#{ none ? 'None-' : '' }Match: #{header}"
end
if_modified_since(unmodified = false) click to toggle source

@!method if_modified_since() @return [nil, Time] @see tools.ietf.org/html/rfc2616#section-14.25 RFC2616, section 14.25 @see if_unmodified_since

# File lib/rackful/request.rb, line 228
def if_modified_since unmodified = false
  header = env["HTTP_IF_#{ unmodified ? 'UN' : '' }MODIFIED_SINCE"]
  return nil unless header
  begin
    header = Time.httpdate( header )
  rescue ArgumentError
    raise HTTP400BadRequest, "Couldn't parse If-#{ unmodified ? 'Unmodified' : 'Modified' }-Since: #{header}"
  end
  header
end
if_none_match() click to toggle source

Parses the HTTP/1.1 ‘If-None-Match:` header. @return [nil, Array<String>] @see tools.ietf.org/html/rfc2616#section-14.26 RFC2616, section 14.26 @see if_match

# File lib/rackful/request.rb, line 219
def if_none_match
  self.if_match true
end
if_unmodified_since() click to toggle source

@return [nil, Time] @see tools.ietf.org/html/rfc2616#section-14.28 RFC2616, section 14.28 @see if_modified_since

# File lib/rackful/request.rb, line 243
def if_unmodified_since
  self.if_modified_since true
end
q_values() click to toggle source

Shortcut to {Rack::Utils.q_values}. Well, actually, we reimplemented it because the implementation in {Rack::Utils} seems incomplete. @return [Array<Array(type, quality)>] @see Rack::Utils.q_values

# File lib/rackful/request.rb, line 156
def q_values
  # This would be the “shortcut” implementation:
  #env['rackful.q_values'] ||= Rack::Utils.q_values(env['HTTP_ACCEPT'])
  # But here’s a full (and better) implementation:
  env['rackful.q_values'] ||= env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).map do
    |part|
    value, *parameters = part.split(/\s*;\s*/)
    quality = 1.0
    parameters.each do |p|
      quality = p[2..-1].to_f if p.start_with? 'q='
    end
    [value, quality]
  end
end
resource() click to toggle source

The current request’s main resource.

As a side effect, {#canonical_uri} can be changed. @return [Resource] @raise [HTTP404NotFound] from {#resource_at}

# File lib/rackful/request.rb, line 40
def resource
  @rackful_request_resource ||= begin
    c = URI(url).normalize
    retval = resource_at(c)
    c += retval.uri
    c.query = query_string unless query_string.empty?
    env['rackful.canonical_uri'] = c
    retval
  end
end
resource_at( uri ) click to toggle source

Calls the code block passed to the {#initialize constructor}. @param uri [URI::HTTP, String] @return [Resource] @raise [HTTP404NotFound]

# File lib/rackful/request.rb, line 27
def resource_at( uri )
  uri = uri.kind_of?( URI::Generic ) ? uri.dup : URI(uri).normalize
  uri.query = nil
  retval = env['rackful.resource_registry'].call( uri )
  raise HTTP404NotFound unless retval
  retval
end
validate_etag(etag, etags) click to toggle source

Does any of the tags in ‘etags` match `etag`? @param etag [#to_s] @param etags [#to_a] @example

etag = '"foo"'
etags = [ 'W/"foo"', '"bar"' ]
validate_etag etag, etags
#> true

@return [Boolean] @see tools.ietf.org/html/rfc2616#section-13.3.3 RFC2616 section 13.3.3

for details about weak and strong validator comparison.
# File lib/rackful/request.rb, line 259
def validate_etag etag, etags
  etag = etag.to_s
  match = etags.to_a.detect do
    |tag|
    tag = tag.to_s
    tag == '*' or
    tag == etag or
    'W/' +  tag == etag or
    'W/' + etag ==  tag
  end
  if  match and
      '*' != match and
      'W/' == etag[0,2] || 'W/' == match[0,2] and
      ! [ 'HEAD', 'GET' ].include? self.request_method
    raise HTTP400BadRequest, "Weak validators are only allowed for GET and HEAD requests."
  end
  !!match
end