Class: Mongrel::HttpRequest
- Inherits:
-
Object
- Object
- Mongrel::HttpRequest
- Defined in:
- lib/mongrel.rb
Overview
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.
Instance Attribute Summary collapse
-
#body ⇒ Object
readonly
Returns the value of attribute body.
-
#params ⇒ Object
readonly
Returns the value of attribute params.
Class Method Summary collapse
-
.escape(s) ⇒ Object
Performs URI escaping so that you can construct proper query strings faster.
-
.query_parse(qs, d = '&;') ⇒ Object
Parses a query string by breaking it up at the ‘&’ and ‘;’ characters.
-
.unescape(s) ⇒ Object
Unescapes a URI escaped string.
Instance Method Summary collapse
-
#initialize(params, socket, dispatchers) ⇒ HttpRequest
constructor
You don’t really call this.
-
#read_body(remain, total) ⇒ Object
Does the heavy lifting of properly reading the larger body requests in small chunks.
- #read_socket(len) ⇒ Object
Constructor Details
#initialize(params, socket, dispatchers) ⇒ HttpRequest
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.
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/mongrel.rb', line 196 def initialize(params, socket, dispatchers) @params = params @socket = socket @dispatchers = dispatchers content_length = @params[Const::CONTENT_LENGTH].to_i remain = content_length - @params.http_body.length # tell all dispatchers the request has begun @dispatchers.each do |dispatcher| dispatcher.request_begins(@params) end unless @dispatchers.nil? || @dispatchers.empty? # 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. if remain <= 0 # we've got everything, pack it up @body = StringIO.new @body.write @params.http_body update_request_progress(0, content_length) elsif remain > 0 # must read more data to complete body if remain > Const::MAX_BODY # huge body, put it in a tempfile @body = Tempfile.new(Const::MONGREL_TMP_BASE) @body.binmode else # small body, just use that @body = StringIO.new end @body.write @params.http_body read_body(remain, content_length) end @body.rewind if @body end |
Instance Attribute Details
#body ⇒ Object (readonly)
Returns the value of attribute body.
191 192 193 |
# File 'lib/mongrel.rb', line 191 def body @body end |
#params ⇒ Object (readonly)
Returns the value of attribute params.
191 192 193 |
# File 'lib/mongrel.rb', line 191 def params @params end |
Class Method Details
.escape(s) ⇒ Object
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).
290 291 292 293 294 |
# File 'lib/mongrel.rb', line 290 def self.escape(s) s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) { '%'+$1.unpack('H2'*$1.size).join('%').upcase }.tr(' ', '+') end |
.query_parse(qs, d = '&;') ⇒ Object
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 ‘&;’.
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/mongrel.rb', line 308 def self.query_parse(qs, d = '&;') params = {} (qs||'').split(/[#{d}] */n).inject(params) { |h,p| k, v=unescape(p).split('=',2) if cur = params[k] if cur.class == Array params[k] << v else params[k] = [cur, v] end else params[k] = v end } return params end |
.unescape(s) ⇒ Object
Unescapes a URI escaped string. (Stolen from Camping).
298 299 300 301 302 |
# File 'lib/mongrel.rb', line 298 def self.unescape(s) s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){ [$1.delete('%')].pack('H*') } end |
Instance Method Details
#read_body(remain, total) ⇒ Object
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.
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/mongrel.rb', line 245 def read_body(remain, total) begin # write the odd sized chunk first @params.http_body = read_socket(remain % Const::CHUNK_SIZE) remain -= @body.write(@params.http_body) update_request_progress(remain, total) # then stream out nothing but perfectly sized chunks until remain <= 0 or @socket.closed? # ASSUME: we are writing to a disk and these writes always write the requested amount @params.http_body = read_socket(Const::CHUNK_SIZE) remain -= @body.write(@params.http_body) update_request_progress(remain, total) end rescue Object => e STDERR.puts "#{Time.now}: Error reading HTTP body: #{e.inspect}" STDERR.puts e.backtrace.join("\n") # any errors means we should delete the file, including if the file is dumped @socket.close rescue nil @body.delete if @body.class == Tempfile @body = nil # signals that there was a problem end end |
#read_socket(len) ⇒ Object
272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/mongrel.rb', line 272 def read_socket(len) if !@socket.closed? data = @socket.read(len) if !data raise "Socket read return nil" elsif data.length != len raise "Socket read returned insufficient data: #{data.length}" else data end else raise "Socket already closed when reading." end end |