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

Attributes

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

Public Class methods

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

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

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

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

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

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

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

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

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

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

[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

[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]