class Mongrel2::Request

The Mongrel2 Request base class. Derivatives of this class represent a request from a Mongrel2 server.

Attributes

request_types[R]
body[R]

The request body data, if there is any, as an IO(ish) object

conn_id[R]

The listener ID on the server

header[R]

The Mongrel2::Table object that contains the request headers

headers[R]

The Mongrel2::Table object that contains the request headers

path[R]

The path component of the requested URL in HTTP, or the equivalent for other request types

raw[R]

The raw request content, if the request was parsed from mongrel2

sender_id[R]

The UUID of the requesting mongrel server

Public Class Methods

new( sender_id, conn_id, path, headers, body='', raw=nil ) click to toggle source

Create a new Request object with the given sender_id, conn_id, path, headers, and body. The optional raw is for the raw request content, which can be useful later for debugging.

# File lib/mongrel2/request.rb, line 113
def initialize( sender_id, conn_id, path, headers, body='', raw=nil )

        @sender_id = sender_id
        @conn_id   = Integer( conn_id )
        @path      = path
        @headers   = Mongrel2::Table.new( headers )
        @raw       = raw

        @body      = self.make_entity_body( body )
        @response  = nil
end
parse( raw_request ) click to toggle source

Parse the given raw_request from a Mongrel2 server and return an appropriate request object.

# File lib/mongrel2/request.rb, line 28
def self::parse( raw_request )
        sender, conn_id, path, rest = raw_request.split( ' ', 4 )
        self.log.debug "Parsing request for %p from %s:%s (rest: %p...)" %
                [ path, sender, conn_id, rest[0,20] ]

        # Extract the headers and the body, ignore the rest
        headers, rest = TNetstring.parse( rest )
        body, _       = TNetstring.parse( rest )

        # Headers will be a JSON String when not using the TNetString protocol
        if headers.is_a?( String )
                self.log.debug "  parsing old-style headers"
                headers = Yajl::Parser.parse( headers )
        end

        # This isn't supposed to happen, but guard against it anyway
        headers['METHOD'] =~ /^(\w+)$/ or
                raise Mongrel2::UnhandledMethodError, headers['METHOD']
        req_method = $1.to_sym
        self.log.debug "Request method is: %p" % [ req_method ]
        concrete_class = self.subclass_for_method( req_method )

        return concrete_class.new( sender, conn_id, path, headers, body, raw_request )
end
register_request_type( subclass, *req_methods ) click to toggle source

Register the specified subclass as the class to instantiate when the METHOD header is one of the specified req_methods. This method exists for frameworks which wish to provide their own Request types.

For example, if your framework has a JSONRequest class that inherits from Mongrel2::JSONRequest, and you want it to be returned from Mongrel2::Request.parse for METHOD=JSON requests:

class MyFramework::JSONRequest < Mongrel2::JSONRequest
    register_request_type self, 'JSON'

    # Override #initialize to do any stuff specific to your
    # request type, but you'll likely want to super() to
    # Mongrel2::JSONRequest.
    def initialize( * )
        super
        # Do some other stuff
    end

end # class MyFramework::JSONRequest

If you wish one of your subclasses to be used instead of Mongrel2::Request for the default request class, register it with a METHOD of :__default.

# File lib/mongrel2/request.rb, line 77
def self::register_request_type( subclass, *req_methods )
        self.log.debug "Registering %p for %p requests" % [ subclass, req_methods ]
        req_methods.each do |methname|
                if methname == :__default
                        # Clear cached lookups
                        self.log.info "Registering %p as the default request type." % [ subclass ]
                        Mongrel2::Request.request_types.delete_if {|_, klass| klass == Mongrel2::Request }
                        Mongrel2::Request.request_types.default_proc = lambda {|h,k| h[k] = subclass }
                else
                        self.log.info "Registering %p for the %p method." % [ subclass, methname ]
                        Mongrel2::Request.request_types[ methname.to_sym ] = subclass
                end
        end
end
response_class() click to toggle source

Return the Mongrel2::Response class that corresponds with the receiver.

# File lib/mongrel2/request.rb, line 100
def self::response_class
        return Mongrel2::Response
end
subclass_for_method( methname ) click to toggle source

Return the Mongrel2::Request class registered for the request method methname.

# File lib/mongrel2/request.rb, line 94
def self::subclass_for_method( methname )
        return Mongrel2::Request.request_types[ methname.to_sym ]
end

Public Instance Methods

body=( newbody ) click to toggle source

Set the request's entity body to newbody. If newbody is a String-ish object (i.e., it responds to to_str), it will be wrapped in a StringIO in 'r+' mode).

# File lib/mongrel2/request.rb, line 153
def body=( newbody )
        newbody = StringIO.new( newbody.dup, 'a+' ) if newbody.respond_to?( :to_str )
        @body = newbody
end
is_disconnect?() click to toggle source

Return true if the request is a special 'disconnect' notification from Mongrel2.

# File lib/mongrel2/request.rb, line 169
def is_disconnect?
        return false
end
remote_ip() click to toggle source

Fetch the original requestor IP address.

# File lib/mongrel2/request.rb, line 175
def remote_ip
        ips = [ self.headers.x_forwarded_for ]
        return IPAddr.new( ips.flatten.first )
end
response() click to toggle source

Create a Mongrel2::Response that will respond to the same server/connection as the receiver. If you wish your specialized Request class to have a corresponding response type, you can override the Mongrel2::Request.response_class method to achieve that.

# File lib/mongrel2/request.rb, line 163
def response
        return @response ||= self.class.response_class.from_request( self )
end

Async Upload Support

↑ top

Public Instance Methods

extended_reply?() click to toggle source

Indicate that a request is never an extended reply.

# File lib/mongrel2/request.rb, line 251
def extended_reply?
        return false
end
server_chroot() click to toggle source

Return the chroot directory of the mongrel2 daemon that received this request as a Pathname.

# File lib/mongrel2/request.rb, line 209
def server_chroot
        route = Mongrel2::Config::Route.for_request( self ) or
                raise Mongrel2::UploadError, "couldn't find the route config for %s" % [ self ]
        server = route.host.server

        path = server.chroot
        path = '/' if path.empty?

        return Pathname( path )
end
upload_done?() click to toggle source

Returns true if this request is an 'asynchronous upload done' notification.

# File lib/mongrel2/request.rb, line 229
def upload_done?
        return self.headers.member?( :x_mongrel2_upload_start ) &&
               self.headers.member?( :x_mongrel2_upload_done )
end
upload_headers_match?() click to toggle source

Returns true if this request is an 'asynchronous upload done' notification and the two headers match (trivial guard against forgery)

# File lib/mongrel2/request.rb, line 237
def upload_headers_match?
        return self.upload_done? &&
               self.headers.x_mongrel2_upload_start == self.headers.x_mongrel2_upload_done
end
upload_started?() click to toggle source

Returns true if this request is an 'asynchronous upload started' notification.

# File lib/mongrel2/request.rb, line 222
def upload_started?
        return self.headers.member?( :x_mongrel2_upload_start ) &&
               !self.headers.member?( :x_mongrel2_upload_done )
end
uploaded_file() click to toggle source

The Pathname, relative to Mongrel2's chroot path, of the uploaded entity body.

# File lib/mongrel2/request.rb, line 187
def uploaded_file
        raise Mongrel2::UploadError, "invalid upload: upload headers don't match" unless
                self.upload_headers_match?

        relpath = Pathname( self.headers.x_mongrel2_upload_done )
        chrooted = self.server_chroot + relpath

        if chrooted.exist?
                return chrooted
        elsif relpath.exist?
                return relpath
        else
                self.log.error "uploaded body %s not found: tried relative to cwd and server chroot (%s)" %
                        [ relpath, chrooted ]
                raise Mongrel2::UploadError,
                        "couldn't find the path to uploaded body %p." % [ chrooted.to_s ]
        end
end
valid_upload?() click to toggle source

Returns true if this request is an asynchronous upload, and the filename of the finished request matches the one from the starting notification.

# File lib/mongrel2/request.rb, line 245
def valid_upload?
        return self.upload_done? && self.upload_headers_match?
end

Introspection Methods

↑ top

Public Instance Methods

inspect() click to toggle source

Returns a string containing a human-readable representation of the Request, suitable for debugging.

# File lib/mongrel2/request.rb, line 270
def inspect
        return "#<%p:0x%016x %s (%s)>" % [
                self.class,
                self.object_id * 2,
                self.inspect_details,
                self.socket_id
        ]
end
socket_id() click to toggle source

Returns a string containing the request's sender and connection IDs separated by a colon.

# File lib/mongrel2/request.rb, line 262
def socket_id
        return "%s:%d" % [ self.sender_id, self.conn_id ]
end

Protected Instance Methods

inspect_details() click to toggle source

Return the details to include in the contents of the inspected object. This method allows other request types to provide their own details while keeping the form somewhat consistent.

# File lib/mongrel2/request.rb, line 317
def inspect_details
        return "%s -- %d headers, %p body" % [
                self.path,
                self.headers.length,
                self.body.class,
        ]
end
make_entity_body( body ) click to toggle source

Convert the entity body into an IOish object, wrapping it in a StringIO if it doesn't already respond to :read, :pos, and :seek. If the request has valid 'X-Mongrel2-Upload-*' headers (the async upload API), a File object opened to the spool file will be returned instead.

# File lib/mongrel2/request.rb, line 288
def make_entity_body( body )
        # :TODO: Handle Content-Encoding, too.

        enc = self.headers.content_type[ /\bcharset=(\S+)/, 1 ] if self.headers.content_type

        if self.valid_upload?
                enc ||= Encoding::ASCII_8BIT
                spoolfile = self.uploaded_file
                self.log.info "Using async %s spool file %s as request entity body." % [ enc, spoolfile ]
                return spoolfile.open( 'r', encoding: enc )

        elsif !( body.respond_to?(:read) && body.respond_to?(:pos) && body.respond_to?(:seek) )
                self.log.info "Wrapping non-IO (%p) body in a StringIO" % [ body.class ] unless
                        body.is_a?( String )

                # Get the object as a String, set the encoding
                str = String.new( body, encoding: enc )
                # str.force_encoding( enc ) if enc && str.encoding == Encoding::ASCII_8BIT

                return StringIO.new( str, 'r+' )
        else
                return body
        end
end