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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
module Mongrel
#
# 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.
#
class HttpRequest
attr_reader :body, :params
# 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, 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
# returns an environment which is rackable
# http://rack.rubyforge.org/doc/files/SPEC.html
def env
env = params.clone
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
# updates all dispatchers about our progress
def update_request_progress(clen, total)
return if @dispatchers.nil? || @dispatchers.empty?
@dispatchers.each do |dispatcher|
dispatcher.request_progress(@params, clen, total)
end
end
private :update_request_progress
# 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.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.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
# 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).
def self.escape(s)
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
'%'+$1.unpack('H2'*$1.size).join('%').upcase
}.tr(' ', '+')
end
# Unescapes a URI escaped string. (Stolen from Camping).
def self.unescape(s)
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
[$1.delete('%')].pack('H*')
}
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 '&;'.
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
end
end
|