| 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.
| body | [R] | |
| body | [R] | |
| params | [R] | |
| params | [R] |
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).
# 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).
# 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.
# 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.
# 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 ’&;’.
# 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 ’&;’.
# 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
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.
# 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.
# 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
# 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
# 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