Class: Mongrel::HttpRequest

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

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

#bodyObject (readonly)

Returns the value of attribute body.



191
192
193
# File 'lib/mongrel.rb', line 191

def body
  @body
end

#paramsObject (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