class Mongrel2::Request
The Mongrel2
Request
base class. Derivatives of this class represent a request from a Mongrel2
server.
Attributes
The request body data, if there is any, as an IO(ish) object
The listener ID on the server
The Mongrel2::Table
object that contains the request headers
The Mongrel2::Table
object that contains the request headers
The path component of the requested URL in HTTP, or the equivalent for other request types
The raw request content, if the request was parsed from mongrel2
The UUID of the requesting mongrel server
Public Class Methods
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 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 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
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
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
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
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
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
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
↑ topPublic Instance Methods
Indicate that a request is never an extended reply.
# File lib/mongrel2/request.rb, line 251 def extended_reply? return false end
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
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
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
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
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
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
↑ topPublic Instance Methods
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
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
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
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