Class: Mongrel::HttpRequest

Class | Mongrel::HttpRequest
In: | lib/mongrel/http_request.rb lib/mongrel/http_request.rb
Parent: | Object

When a handler is found for a registered URI then this class is constructed and passed to your HttpHandler::process method. You should assume that one handler processes all requests. Included in the HttpRequest is a HttpRequest.params Hash that matches common CGI params, and a HttpRequest.body which is a string containing the request body (raw for now).

The HttpRequest.initialize method will convert any request that is larger than Const::MAX_BODY into a Tempfile and use that as the body. Otherwise it uses a StringIO object. To be safe, you should assume it works like a file.

The HttpHandler.request_notify system is implemented by having HttpRequest call HttpHandler.request_begins, HttpHandler.request_progress, HttpHandler.process during the IO processing. This adds a small amount of overhead but lets you implement finer controlled handlers and filters.

### Methods

escape escape new new query_parse query_parse read_body read_body read_socket read_socket unescape unescape

### Attributes

body | [R] |
body | [R] |
params | [R] |
params | [R] |

### Public Class methods

escape(s)

Performs URI escaping so that you can construct proper query strings faster. Use this rather than the cgi.rb version since it‘s faster. (Stolen from Camping).

[Source]

         # File lib/mongrel/http_request.rb, line 119
    119:     def self.escape(s)
    120:       s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
    121:         '%'+$1.unpack('H2'*$1.size).join('%').upcase
    122:       }.tr(' ', '+')
    123:     end

escape(s)

Performs URI escaping so that you can construct proper query strings faster. Use this rather than the cgi.rb version since it‘s faster. (Stolen from Camping).

[Source]

         # File lib/mongrel/http_request.rb, line 119
    119:     def self.escape(s)
    120:       s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
    121:         '%'+$1.unpack('H2'*$1.size).join('%').upcase
    122:       }.tr(' ', '+')
    123:     end

new(params, socket, dispatchers)

You don‘t really call this. It‘s made for you. Main thing it does is hook up the params, and store any remaining body data into the HttpRequest.body attribute.

[Source]

        # File lib/mongrel/http_request.rb, line 25
    25:     def initialize(params, socket, dispatchers)
    26:       @params = params
    27:       @socket = socket
    28:       @dispatchers = dispatchers
    29:       content_length = @params[Const::CONTENT_LENGTH].to_i
    30:       remain = content_length - @params.http_body.length
    31:
    32:       # tell all dispatchers the request has begun
    33:       @dispatchers.each do |dispatcher|
    34:         dispatcher.request_begins(@params)
    35:       end unless @dispatchers.nil? || @dispatchers.empty?
    36:
    37:       # Some clients (like FF1.0) report 0 for body and then send a body.  This will probably truncate them but at least the request goes through usually.
    38:       if remain <= 0
    39:         # we've got everything, pack it up
    40:         @body = StringIO.new
    41:         @body.write @params.http_body
    42:         update_request_progress(0, content_length)
    43:       elsif remain > 0
    44:         # must read more data to complete body
    45:         if remain > Const::MAX_BODY
    46:           # huge body, put it in a tempfile
    47:           @body = Tempfile.new(Const::MONGREL_TMP_BASE)
    48:           @body.binmode
    49:         else
    50:           # small body, just use that
    51:           @body = StringIO.new
    52:         end
    53:
    54:         @body.write @params.http_body
    55:         read_body(remain, content_length)
    56:       end
    57:
    58:       @body.rewind if @body
    59:     end

new(params, socket, dispatchers)

You don‘t really call this. It‘s made for you. Main thing it does is hook up the params, and store any remaining body data into the HttpRequest.body attribute.

[Source]

        # File lib/mongrel/http_request.rb, line 25
    25:     def initialize(params, socket, dispatchers)
    26:       @params = params
    27:       @socket = socket
    28:       @dispatchers = dispatchers
    29:       content_length = @params[Const::CONTENT_LENGTH].to_i
    30:       remain = content_length - @params.http_body.length
    31:
    32:       # tell all dispatchers the request has begun
    33:       @dispatchers.each do |dispatcher|
    34:         dispatcher.request_begins(@params)
    35:       end unless @dispatchers.nil? || @dispatchers.empty?
    36:
    37:       # Some clients (like FF1.0) report 0 for body and then send a body.  This will probably truncate them but at least the request goes through usually.
    38:       if remain <= 0
    39:         # we've got everything, pack it up
    40:         @body = StringIO.new
    41:         @body.write @params.http_body
    42:         update_request_progress(0, content_length)
    43:       elsif remain > 0
    44:         # must read more data to complete body
    45:         if remain > Const::MAX_BODY
    46:           # huge body, put it in a tempfile
    47:           @body = Tempfile.new(Const::MONGREL_TMP_BASE)
    48:           @body.binmode
    49:         else
    50:           # small body, just use that
    51:           @body = StringIO.new
    52:         end
    53:
    54:         @body.write @params.http_body
    55:         read_body(remain, content_length)
    56:       end
    57:
    58:       @body.rewind if @body
    59:     end

query_parse(qs, d = '&;')

Parses a query string by breaking it up at the ’&’ and ’;’ characters. You can also use this to parse cookies by changing the characters used in the second parameter (which defaults to ’&;’.

[Source]

         # File lib/mongrel/http_request.rb, line 137
    137:     def self.query_parse(qs, d = '&;')
    138:       params = {}
    139:       (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
    140:         k, v=unescape(p).split('=',2)
    141:         if cur = params[k]
    142:           if cur.class == Array
    143:             params[k] << v
    144:           else
    145:             params[k] = [cur, v]
    146:           end
    147:         else
    148:           params[k] = v
    149:         end
    150:       }
    151:
    152:       return params
    153:     end

query_parse(qs, d = '&;')

Parses a query string by breaking it up at the ’&’ and ’;’ characters. You can also use this to parse cookies by changing the characters used in the second parameter (which defaults to ’&;’.

[Source]

         # File lib/mongrel/http_request.rb, line 137
    137:     def self.query_parse(qs, d = '&;')
    138:       params = {}
    139:       (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
    140:         k, v=unescape(p).split('=',2)
    141:         if cur = params[k]
    142:           if cur.class == Array
    143:             params[k] << v
    144:           else
    145:             params[k] = [cur, v]
    146:           end
    147:         else
    148:           params[k] = v
    149:         end
    150:       }
    151:
    152:       return params
    153:     end

unescape(s)

Unescapes a URI escaped string. (Stolen from Camping).

[Source]

         # File lib/mongrel/http_request.rb, line 127
    127:     def self.unescape(s)
    128:       s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
    129:         [$1.delete('%')].pack('H*')
    130:       }
    131:     end

unescape(s)

Unescapes a URI escaped string. (Stolen from Camping).

[Source]

         # File lib/mongrel/http_request.rb, line 127
    127:     def self.unescape(s)
    128:       s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
    129:         [$1.delete('%')].pack('H*')
    130:       }
    131:     end

### Public Instance methods

read_body(remain, total)

Does the heavy lifting of properly reading the larger body requests in small chunks. It expects @body to be an IO object, @socket to be valid, and will set @body = nil if the request fails. It also expects any initial part of the body that has been read to be in the @body already.

[Source]

        # File lib/mongrel/http_request.rb, line 74
    74:     def read_body(remain, total)
    75:       begin
    76:         # write the odd sized chunk first
    77:         @params.http_body = read_socket(remain % Const::CHUNK_SIZE)
    78:
    79:         remain -= @body.write(@params.http_body)
    80:
    81:         update_request_progress(remain, total)
    82:
    83:         # then stream out nothing but perfectly sized chunks
    84:         until remain <= 0 or @socket.closed?
    85:           # ASSUME: we are writing to a disk and these writes always write the requested amount
    86:           @params.http_body = read_socket(Const::CHUNK_SIZE)
    87:           remain -= @body.write(@params.http_body)
    88:
    89:           update_request_progress(remain, total)
    90:         end
    91:       rescue Object => e
    92:         STDERR.puts "#{Time.now}: Error reading HTTP body: #{e.inspect}"
    93:         STDERR.puts e.backtrace.join("\n")
    94:         # any errors means we should delete the file, including if the file is dumped
    95:         @socket.close rescue nil
    96:         @body.delete if @body.class == Tempfile
    97:         @body = nil # signals that there was a problem
    98:       end
    99:     end

read_body(remain, total)

Does the heavy lifting of properly reading the larger body requests in small chunks. It expects @body to be an IO object, @socket to be valid, and will set @body = nil if the request fails. It also expects any initial part of the body that has been read to be in the @body already.

[Source]

        # File lib/mongrel/http_request.rb, line 74
    74:     def read_body(remain, total)
    75:       begin
    76:         # write the odd sized chunk first
    77:         @params.http_body = read_socket(remain % Const::CHUNK_SIZE)
    78:
    79:         remain -= @body.write(@params.http_body)
    80:
    81:         update_request_progress(remain, total)
    82:
    83:         # then stream out nothing but perfectly sized chunks
    84:         until remain <= 0 or @socket.closed?
    85:           # ASSUME: we are writing to a disk and these writes always write the requested amount
    86:           @params.http_body = read_socket(Const::CHUNK_SIZE)
    87:           remain -= @body.write(@params.http_body)
    88:
    89:           update_request_progress(remain, total)
    90:         end
    91:       rescue Object => e
    92:         STDERR.puts "#{Time.now}: Error reading HTTP body: #{e.inspect}"
    93:         STDERR.puts e.backtrace.join("\n")
    94:         # any errors means we should delete the file, including if the file is dumped
    95:         @socket.close rescue nil
    96:         @body.delete if @body.class == Tempfile
    97:         @body = nil # signals that there was a problem
    98:       end
    99:     end

read_socket(len)

[Source]

         # File lib/mongrel/http_request.rb, line 101
    101:     def read_socket(len)
    102:       if !@socket.closed?
    103:         data = @socket.read(len)
    104:         if !data
    105:           raise "Socket read return nil"
    106:         elsif data.length != len
    107:           raise "Socket read returned insufficient data: #{data.length}"
    108:         else
    109:           data
    110:         end
    111:       else
    112:         raise "Socket already closed when reading."
    113:       end
    114:     end

read_socket(len)

[Source]

         # File lib/mongrel/http_request.rb, line 101
    101:     def read_socket(len)
    102:       if !@socket.closed?
    103:         data = @socket.read(len)
    104:         if !data
    105:           raise "Socket read return nil"
    106:         elsif data.length != len
    107:           raise "Socket read returned insufficient data: #{data.length}"
    108:         else
    109:           data
    110:         end
    111:       else
    112:         raise "Socket already closed when reading."
    113:       end
    114:     end

[Validate]