1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
| |
module Mongrel
#
# 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.
#
class HttpRequest
attr_reader :body, :params, :logger
# 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.
def initialize(params, socket, logger)
@params = params
@socket = socket
@logger = logger
content_length = @params[Const::CONTENT_LENGTH].to_i
remain = content_length - @params[Const::HTTP_BODY].length
# 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[Const::HTTP_BODY]
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[Const::HTTP_BODY]
read_body(remain, content_length)
end
@body.rewind if @body
end
# Returns an environment which is rackable: http://rack.rubyforge.org/doc/files/SPEC.html
# Copied directly from Rack's old Mongrel handler.
def env
env = params.clone
env["QUERY_STRING"] ||= ''
env.delete "HTTP_CONTENT_TYPE"
env.delete "HTTP_CONTENT_LENGTH"
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({"rack.version" => [0,1],
"rack.input" => @body,
"rack.errors" => STDERR,
"rack.multithread" => true,
"rack.multiprocess" => false, # ???
"rack.run_once" => false,
"rack.url_scheme" => "http",
})
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.
def read_body(remain, total)
begin
# Write the odd sized chunk first
@params[Const::HTTP_BODY] = read_socket(remain % Const::CHUNK_SIZE)
remain -= @body.write(@params[Const::HTTP_BODY])
# 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[Const::HTTP_BODY] = read_socket(Const::CHUNK_SIZE)
remain -= @body.write(@params[Const::HTTP_BODY])
end
rescue Object => e
logger.error "Error reading HTTP body: #{e.inspect}"
# Any errors means we should delete the file, including if the file is dumped
@socket.close rescue nil
@body.close! if @body.class == Tempfile
@body = nil # signals that there was a problem
end
end
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
end
end
|