The Mongrel2 Request base class. Derivatives of this class represent a request from a Mongrel2 server.
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
Create a new Request object with the given
sender_id, conn_id, path,
headers, and body. The optional nil
is for the raw request content, which can be useful later for debugging.
# File lib/mongrel2/request.rb, line 114
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'] =~ %r^(\w+)$/ or
raise Mongrel2::UnhandledMethodError, headers['METHOD']
req_method = $1.untaint.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 ::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 78
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 101
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 95
def self::subclass_for_method( methname )
return Mongrel2::Request.request_types[ methname.to_sym ]
end
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 154
def body=( newbody )
newbody = StringIO.new( newbody, '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 170
def is_disconnect?
return false
end
Fetch the original requestor IP address.
# File lib/mongrel2/request.rb, line 176
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 ::response_class method to achieve that.
# File lib/mongrel2/request.rb, line 164
def response
return @response ||= self.class.response_class.from_request( self )
end
See mongrel2.org/static/book-finalch6.html#x8-810005.5 for details.
Returns true if this request is an ‘asynchronous upload done’
notification.
# File lib/mongrel2/request.rb, line 220
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 228
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 213
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 188
def uploaded_file
raise Mongrel2::UploadError, "invalid upload: upload headers don't match" unless
self.upload_headers_match?
route = Mongrel2::Config::Route.for_request( self ) or
raise Mongrel2::UploadError, "couldn't find the route config for %s" % [ self ]
server = route.host.server
relpath = Pathname( self.headers.x_mongrel2_upload_done )
chrooted = server.chroot_path + 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, server.chroot ]
raise Mongrel2::UploadError,
"couldn't find the path to uploaded body."
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 236
def valid_upload?
return self.upload_done? && self.upload_headers_match?
end
Returns a string containing a human-readable representation of the Request, suitable for debugging.
# File lib/mongrel2/request.rb, line 247
def inspect
return "#<%p:0x%016x %s (%s/%d)>" % [
self.class,
self.object_id * 2,
self.inspect_details,
self.sender_id,
self.conn_id
]
end
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 294
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 266
def make_entity_body( body )
# :TODO: Handle Content-Encoding, too.
enc = self.headers.content_type[ %r\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 ]
# Get the object as a String, set the encoding
str = body.to_s
str.force_encoding( enc ) if enc && str.encoding == Encoding::ASCII_8BIT
return StringIO.new( str, 'r+' )
else
return body
end
end