mongrel 1.1.2-java
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mongrel might be problematic. Click here for more details.
- data.tar.gz.sig +1 -0
- data/CHANGELOG +12 -0
- data/COPYING +55 -0
- data/LICENSE +55 -0
- data/Manifest +69 -0
- data/README +74 -0
- data/TODO +5 -0
- data/bin/mongrel_rails +283 -0
- data/examples/builder.rb +29 -0
- data/examples/camping/README +3 -0
- data/examples/camping/blog.rb +294 -0
- data/examples/camping/tepee.rb +149 -0
- data/examples/httpd.conf +474 -0
- data/examples/mime.yaml +3 -0
- data/examples/mongrel.conf +9 -0
- data/examples/mongrel_simple_ctrl.rb +92 -0
- data/examples/mongrel_simple_service.rb +116 -0
- data/examples/monitrc +57 -0
- data/examples/random_thrash.rb +19 -0
- data/examples/simpletest.rb +52 -0
- data/examples/webrick_compare.rb +20 -0
- data/ext/http11/ext_help.h +14 -0
- data/ext/http11/extconf.rb +6 -0
- data/ext/http11/http11.c +402 -0
- data/ext/http11/http11_parser.c +1221 -0
- data/ext/http11/http11_parser.h +49 -0
- data/ext/http11/http11_parser.java.rl +170 -0
- data/ext/http11/http11_parser.rl +152 -0
- data/ext/http11/http11_parser_common.rl +54 -0
- data/ext/http11_java/Http11Service.java +13 -0
- data/ext/http11_java/org/jruby/mongrel/Http11.java +266 -0
- data/ext/http11_java/org/jruby/mongrel/Http11Parser.java +572 -0
- data/lib/http11.jar +0 -0
- data/lib/mongrel.rb +355 -0
- data/lib/mongrel/camping.rb +107 -0
- data/lib/mongrel/cgi.rb +181 -0
- data/lib/mongrel/command.rb +222 -0
- data/lib/mongrel/configurator.rb +388 -0
- data/lib/mongrel/const.rb +110 -0
- data/lib/mongrel/debug.rb +203 -0
- data/lib/mongrel/gems.rb +22 -0
- data/lib/mongrel/handlers.rb +468 -0
- data/lib/mongrel/header_out.rb +28 -0
- data/lib/mongrel/http_request.rb +155 -0
- data/lib/mongrel/http_response.rb +163 -0
- data/lib/mongrel/init.rb +10 -0
- data/lib/mongrel/mime_types.yml +616 -0
- data/lib/mongrel/rails.rb +185 -0
- data/lib/mongrel/stats.rb +89 -0
- data/lib/mongrel/tcphack.rb +18 -0
- data/lib/mongrel/uri_classifier.rb +76 -0
- data/mongrel-public_cert.pem +20 -0
- data/mongrel.gemspec +263 -0
- data/setup.rb +1585 -0
- data/test/mime.yaml +3 -0
- data/test/mongrel.conf +1 -0
- data/test/test_cgi_wrapper.rb +26 -0
- data/test/test_command.rb +86 -0
- data/test/test_conditional.rb +107 -0
- data/test/test_configurator.rb +87 -0
- data/test/test_debug.rb +25 -0
- data/test/test_handlers.rb +103 -0
- data/test/test_http11.rb +156 -0
- data/test/test_redirect_handler.rb +44 -0
- data/test/test_request_progress.rb +99 -0
- data/test/test_response.rb +127 -0
- data/test/test_stats.rb +35 -0
- data/test/test_uriclassifier.rb +261 -0
- data/test/test_ws.rb +115 -0
- data/test/testhelp.rb +66 -0
- data/tools/trickletest.rb +45 -0
- metadata +186 -0
- metadata.gz.sig +4 -0
data/lib/http11.jar
ADDED
Binary file
|
data/lib/mongrel.rb
ADDED
@@ -0,0 +1,355 @@
|
|
1
|
+
|
2
|
+
# Standard libraries
|
3
|
+
require 'socket'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'yaml'
|
6
|
+
require 'time'
|
7
|
+
require 'etc'
|
8
|
+
require 'uri'
|
9
|
+
require 'stringio'
|
10
|
+
|
11
|
+
# Compiled Mongrel extension
|
12
|
+
require 'http11'
|
13
|
+
|
14
|
+
# Gem conditional loader
|
15
|
+
require 'mongrel/gems'
|
16
|
+
Mongrel::Gems.require 'cgi_multipart_eof_fix'
|
17
|
+
Mongrel::Gems.require 'fastthread'
|
18
|
+
require 'thread'
|
19
|
+
|
20
|
+
# Ruby Mongrel
|
21
|
+
require 'mongrel/cgi'
|
22
|
+
require 'mongrel/handlers'
|
23
|
+
require 'mongrel/command'
|
24
|
+
require 'mongrel/tcphack'
|
25
|
+
require 'mongrel/configurator'
|
26
|
+
require 'mongrel/uri_classifier'
|
27
|
+
require 'mongrel/const'
|
28
|
+
require 'mongrel/http_request'
|
29
|
+
require 'mongrel/header_out'
|
30
|
+
require 'mongrel/http_response'
|
31
|
+
|
32
|
+
# Mongrel module containing all of the classes (include C extensions) for running
|
33
|
+
# a Mongrel web server. It contains a minimalist HTTP server with just enough
|
34
|
+
# functionality to service web application requests fast as possible.
|
35
|
+
module Mongrel
|
36
|
+
|
37
|
+
# Used to stop the HttpServer via Thread.raise.
|
38
|
+
class StopServer < Exception; end
|
39
|
+
|
40
|
+
# Thrown at a thread when it is timed out.
|
41
|
+
class TimeoutError < Exception; end
|
42
|
+
|
43
|
+
# A Hash with one extra parameter for the HTTP body, used internally.
|
44
|
+
class HttpParams < Hash
|
45
|
+
attr_accessor :http_body
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier
|
50
|
+
# make up the majority of how the server functions. It's a very simple class that just
|
51
|
+
# has a thread accepting connections and a simple HttpServer.process_client function
|
52
|
+
# to do the heavy lifting with the IO and Ruby.
|
53
|
+
#
|
54
|
+
# You use it by doing the following:
|
55
|
+
#
|
56
|
+
# server = HttpServer.new("0.0.0.0", 3000)
|
57
|
+
# server.register("/stuff", MyNiftyHandler.new)
|
58
|
+
# server.run.join
|
59
|
+
#
|
60
|
+
# The last line can be just server.run if you don't want to join the thread used.
|
61
|
+
# If you don't though Ruby will mysteriously just exit on you.
|
62
|
+
#
|
63
|
+
# Ruby's thread implementation is "interesting" to say the least. Experiments with
|
64
|
+
# *many* different types of IO processing simply cannot make a dent in it. Future
|
65
|
+
# releases of Mongrel will find other creative ways to make threads faster, but don't
|
66
|
+
# hold your breath until Ruby 1.9 is actually finally useful.
|
67
|
+
class HttpServer
|
68
|
+
attr_reader :acceptor
|
69
|
+
attr_reader :workers
|
70
|
+
attr_reader :classifier
|
71
|
+
attr_reader :host
|
72
|
+
attr_reader :port
|
73
|
+
attr_reader :throttle
|
74
|
+
attr_reader :timeout
|
75
|
+
attr_reader :num_processors
|
76
|
+
|
77
|
+
# Creates a working server on host:port (strange things happen if port isn't a Number).
|
78
|
+
# Use HttpServer::run to start the server and HttpServer.acceptor.join to
|
79
|
+
# join the thread that's processing incoming requests on the socket.
|
80
|
+
#
|
81
|
+
# The num_processors optional argument is the maximum number of concurrent
|
82
|
+
# processors to accept, anything over this is closed immediately to maintain
|
83
|
+
# server processing performance. This may seem mean but it is the most efficient
|
84
|
+
# way to deal with overload. Other schemes involve still parsing the client's request
|
85
|
+
# which defeats the point of an overload handling system.
|
86
|
+
#
|
87
|
+
# The throttle parameter is a sleep timeout (in hundredths of a second) that is placed between
|
88
|
+
# socket.accept calls in order to give the server a cheap throttle time. It defaults to 0 and
|
89
|
+
# actually if it is 0 then the sleep is not done at all.
|
90
|
+
def initialize(host, port, num_processors=950, throttle=0, timeout=60)
|
91
|
+
|
92
|
+
tries = 0
|
93
|
+
@socket = TCPServer.new(host, port)
|
94
|
+
|
95
|
+
@classifier = URIClassifier.new
|
96
|
+
@host = host
|
97
|
+
@port = port
|
98
|
+
@workers = ThreadGroup.new
|
99
|
+
@throttle = throttle
|
100
|
+
@num_processors = num_processors
|
101
|
+
@timeout = timeout
|
102
|
+
end
|
103
|
+
|
104
|
+
# Does the majority of the IO processing. It has been written in Ruby using
|
105
|
+
# about 7 different IO processing strategies and no matter how it's done
|
106
|
+
# the performance just does not improve. It is currently carefully constructed
|
107
|
+
# to make sure that it gets the best possible performance, but anyone who
|
108
|
+
# thinks they can make it faster is more than welcome to take a crack at it.
|
109
|
+
def process_client(client)
|
110
|
+
begin
|
111
|
+
parser = HttpParser.new
|
112
|
+
params = HttpParams.new
|
113
|
+
request = nil
|
114
|
+
data = client.readpartial(Const::CHUNK_SIZE)
|
115
|
+
nparsed = 0
|
116
|
+
|
117
|
+
# Assumption: nparsed will always be less since data will get filled with more
|
118
|
+
# after each parsing. If it doesn't get more then there was a problem
|
119
|
+
# with the read operation on the client socket. Effect is to stop processing when the
|
120
|
+
# socket can't fill the buffer for further parsing.
|
121
|
+
while nparsed < data.length
|
122
|
+
nparsed = parser.execute(params, data, nparsed)
|
123
|
+
|
124
|
+
if parser.finished?
|
125
|
+
if not params[Const::REQUEST_PATH]
|
126
|
+
# it might be a dumbass full host request header
|
127
|
+
uri = URI.parse(params[Const::REQUEST_URI])
|
128
|
+
params[Const::REQUEST_PATH] = uri.path
|
129
|
+
end
|
130
|
+
|
131
|
+
raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
|
132
|
+
|
133
|
+
script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH])
|
134
|
+
|
135
|
+
if handlers
|
136
|
+
params[Const::PATH_INFO] = path_info
|
137
|
+
params[Const::SCRIPT_NAME] = script_name
|
138
|
+
|
139
|
+
# From http://www.ietf.org/rfc/rfc3875 :
|
140
|
+
# "Script authors should be aware that the REMOTE_ADDR and REMOTE_HOST
|
141
|
+
# meta-variables (see sections 4.1.8 and 4.1.9) may not identify the
|
142
|
+
# ultimate source of the request. They identify the client for the
|
143
|
+
# immediate request to the server; that client may be a proxy, gateway,
|
144
|
+
# or other intermediary acting on behalf of the actual source client."
|
145
|
+
params[Const::REMOTE_ADDR] = client.peeraddr.last
|
146
|
+
|
147
|
+
# select handlers that want more detailed request notification
|
148
|
+
notifiers = handlers.select { |h| h.request_notify }
|
149
|
+
request = HttpRequest.new(params, client, notifiers)
|
150
|
+
|
151
|
+
# in the case of large file uploads the user could close the socket, so skip those requests
|
152
|
+
break if request.body == nil # nil signals from HttpRequest::initialize that the request was aborted
|
153
|
+
|
154
|
+
# request is good so far, continue processing the response
|
155
|
+
response = HttpResponse.new(client)
|
156
|
+
|
157
|
+
# Process each handler in registered order until we run out or one finalizes the response.
|
158
|
+
handlers.each do |handler|
|
159
|
+
handler.process(request, response)
|
160
|
+
break if response.done or client.closed?
|
161
|
+
end
|
162
|
+
|
163
|
+
# And finally, if nobody closed the response off, we finalize it.
|
164
|
+
unless response.done or client.closed?
|
165
|
+
response.finished
|
166
|
+
end
|
167
|
+
else
|
168
|
+
# Didn't find it, return a stock 404 response.
|
169
|
+
client.write(Const::ERROR_404_RESPONSE)
|
170
|
+
end
|
171
|
+
|
172
|
+
break #done
|
173
|
+
else
|
174
|
+
# Parser is not done, queue up more data to read and continue parsing
|
175
|
+
chunk = client.readpartial(Const::CHUNK_SIZE)
|
176
|
+
break if !chunk or chunk.length == 0 # read failed, stop processing
|
177
|
+
|
178
|
+
data << chunk
|
179
|
+
if data.length >= Const::MAX_HEADER
|
180
|
+
raise HttpParserError.new("HEADER is longer than allowed, aborting client early.")
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
185
|
+
client.close rescue nil
|
186
|
+
rescue HttpParserError => e
|
187
|
+
STDERR.puts "#{Time.now}: HTTP parse error, malformed request (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}"
|
188
|
+
STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
|
189
|
+
rescue Errno::EMFILE
|
190
|
+
reap_dead_workers('too many files')
|
191
|
+
rescue Object => e
|
192
|
+
STDERR.puts "#{Time.now}: Read error: #{e.inspect}"
|
193
|
+
STDERR.puts e.backtrace.join("\n")
|
194
|
+
ensure
|
195
|
+
begin
|
196
|
+
client.close
|
197
|
+
rescue IOError
|
198
|
+
# Already closed
|
199
|
+
rescue Object => e
|
200
|
+
STDERR.puts "#{Time.now}: Client error: #{e.inspect}"
|
201
|
+
STDERR.puts e.backtrace.join("\n")
|
202
|
+
end
|
203
|
+
request.body.delete if request and request.body.class == Tempfile
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Used internally to kill off any worker threads that have taken too long
|
208
|
+
# to complete processing. Only called if there are too many processors
|
209
|
+
# currently servicing. It returns the count of workers still active
|
210
|
+
# after the reap is done. It only runs if there are workers to reap.
|
211
|
+
def reap_dead_workers(reason='unknown')
|
212
|
+
if @workers.list.length > 0
|
213
|
+
STDERR.puts "#{Time.now}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'"
|
214
|
+
error_msg = "Mongrel timed out this thread: #{reason}"
|
215
|
+
mark = Time.now
|
216
|
+
@workers.list.each do |worker|
|
217
|
+
worker[:started_on] = Time.now if not worker[:started_on]
|
218
|
+
|
219
|
+
if mark - worker[:started_on] > @timeout + @throttle
|
220
|
+
STDERR.puts "Thread #{worker.inspect} is too old, killing."
|
221
|
+
worker.raise(TimeoutError.new(error_msg))
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
return @workers.list.length
|
227
|
+
end
|
228
|
+
|
229
|
+
# Performs a wait on all the currently running threads and kills any that take
|
230
|
+
# too long. It waits by @timeout seconds, which can be set in .initialize or
|
231
|
+
# via mongrel_rails. The @throttle setting does extend this waiting period by
|
232
|
+
# that much longer.
|
233
|
+
def graceful_shutdown
|
234
|
+
while reap_dead_workers("shutdown") > 0
|
235
|
+
STDERR.puts "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout + @throttle} seconds."
|
236
|
+
sleep @timeout / 10
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def configure_socket_options
|
241
|
+
case RUBY_PLATFORM
|
242
|
+
when /linux/
|
243
|
+
# 9 is currently TCP_DEFER_ACCEPT
|
244
|
+
$tcp_defer_accept_opts = [Socket::SOL_TCP, 9, 1]
|
245
|
+
$tcp_cork_opts = [Socket::SOL_TCP, 3, 1]
|
246
|
+
when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
|
247
|
+
# Do nothing, just closing a bug when freebsd <= 5.4
|
248
|
+
when /freebsd/
|
249
|
+
# Use the HTTP accept filter if available.
|
250
|
+
# The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg
|
251
|
+
unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
|
252
|
+
$tcp_defer_accept_opts = [Socket::SOL_SOCKET, Socket::SO_ACCEPTFILTER, ['httpready', nil].pack('a16a240')]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Runs the thing. It returns the thread used so you can "join" it. You can also
|
258
|
+
# access the HttpServer::acceptor attribute to get the thread later.
|
259
|
+
def run
|
260
|
+
BasicSocket.do_not_reverse_lookup=true
|
261
|
+
|
262
|
+
configure_socket_options
|
263
|
+
|
264
|
+
if defined?($tcp_defer_accept_opts) and $tcp_defer_accept_opts
|
265
|
+
@socket.setsockopt(*$tcp_defer_accept_opts) rescue nil
|
266
|
+
end
|
267
|
+
|
268
|
+
@acceptor = Thread.new do
|
269
|
+
begin
|
270
|
+
while true
|
271
|
+
begin
|
272
|
+
client = @socket.accept
|
273
|
+
|
274
|
+
if defined?($tcp_cork_opts) and $tcp_cork_opts
|
275
|
+
client.setsockopt(*$tcp_cork_opts) rescue nil
|
276
|
+
end
|
277
|
+
|
278
|
+
worker_list = @workers.list
|
279
|
+
|
280
|
+
if worker_list.length >= @num_processors
|
281
|
+
STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection."
|
282
|
+
client.close rescue nil
|
283
|
+
reap_dead_workers("max processors")
|
284
|
+
else
|
285
|
+
thread = Thread.new(client) {|c| process_client(c) }
|
286
|
+
thread[:started_on] = Time.now
|
287
|
+
@workers.add(thread)
|
288
|
+
|
289
|
+
sleep @throttle/100.0 if @throttle > 0
|
290
|
+
end
|
291
|
+
rescue StopServer
|
292
|
+
break
|
293
|
+
rescue Errno::EMFILE
|
294
|
+
reap_dead_workers("too many open files")
|
295
|
+
sleep 0.5
|
296
|
+
rescue Errno::ECONNABORTED
|
297
|
+
# client closed the socket even before accept
|
298
|
+
client.close rescue nil
|
299
|
+
rescue Object => e
|
300
|
+
STDERR.puts "#{Time.now}: Unhandled listen loop exception #{e.inspect}."
|
301
|
+
STDERR.puts e.backtrace.join("\n")
|
302
|
+
end
|
303
|
+
end
|
304
|
+
graceful_shutdown
|
305
|
+
ensure
|
306
|
+
@socket.close
|
307
|
+
# STDERR.puts "#{Time.now}: Closed socket."
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
return @acceptor
|
312
|
+
end
|
313
|
+
|
314
|
+
# Simply registers a handler with the internal URIClassifier. When the URI is
|
315
|
+
# found in the prefix of a request then your handler's HttpHandler::process method
|
316
|
+
# is called. See Mongrel::URIClassifier#register for more information.
|
317
|
+
#
|
318
|
+
# If you set in_front=true then the passed in handler will be put in the front of the list
|
319
|
+
# for that particular URI. Otherwise it's placed at the end of the list.
|
320
|
+
def register(uri, handler, in_front=false)
|
321
|
+
begin
|
322
|
+
@classifier.register(uri, [handler])
|
323
|
+
rescue URIClassifier::RegistrationError
|
324
|
+
handlers = @classifier.resolve(uri)[2]
|
325
|
+
method_name = in_front ? 'unshift' : 'push'
|
326
|
+
handlers.send(method_name, handler)
|
327
|
+
end
|
328
|
+
handler.listener = self
|
329
|
+
end
|
330
|
+
|
331
|
+
# Removes any handlers registered at the given URI. See Mongrel::URIClassifier#unregister
|
332
|
+
# for more information. Remember this removes them *all* so the entire
|
333
|
+
# processing chain goes away.
|
334
|
+
def unregister(uri)
|
335
|
+
@classifier.unregister(uri)
|
336
|
+
end
|
337
|
+
|
338
|
+
# Stops the acceptor thread and then causes the worker threads to finish
|
339
|
+
# off the request queue before finally exiting.
|
340
|
+
def stop(synchronous=false)
|
341
|
+
@acceptor.raise(StopServer.new)
|
342
|
+
|
343
|
+
if synchronous
|
344
|
+
sleep(0.5) while @acceptor.alive?
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# Load experimental library, if present. We put it here so it can override anything
|
352
|
+
# in regular Mongrel.
|
353
|
+
|
354
|
+
$LOAD_PATH.unshift 'projects/mongrel_experimental/lib/'
|
355
|
+
Mongrel::Gems.require 'mongrel_experimental', ">=#{Mongrel::Const::MONGREL_VERSION}"
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Copyright (c) 2005 Zed A. Shaw
|
2
|
+
# You can redistribute it and/or modify it under the same terms as Ruby.
|
3
|
+
#
|
4
|
+
# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
|
5
|
+
# for more information.
|
6
|
+
|
7
|
+
require 'mongrel'
|
8
|
+
|
9
|
+
|
10
|
+
module Mongrel
|
11
|
+
# Support for the Camping micro framework at http://camping.rubyforge.org
|
12
|
+
# This implements the unusually long Postamble that Camping usually
|
13
|
+
# needs and shrinks it down to just a single line or two.
|
14
|
+
#
|
15
|
+
# Your Postamble would now be:
|
16
|
+
#
|
17
|
+
# Mongrel::Camping::start("0.0.0.0",3001,"/tepee",Tepee).join
|
18
|
+
#
|
19
|
+
# If you wish to get fancier than this then you can use the
|
20
|
+
# Camping::CampingHandler directly instead and do your own
|
21
|
+
# wiring:
|
22
|
+
#
|
23
|
+
# h = Mongrel::HttpServer.new(server, port)
|
24
|
+
# h.register(uri, CampingHandler.new(Tepee))
|
25
|
+
# h.register("/favicon.ico", Mongrel::Error404Handler.new(""))
|
26
|
+
#
|
27
|
+
# I add the /favicon.ico since camping apps typically don't
|
28
|
+
# have them and it's just annoying anyway.
|
29
|
+
module Camping
|
30
|
+
|
31
|
+
# This is a specialized handler for Camping applications
|
32
|
+
# that has them process the request and then translates
|
33
|
+
# the results into something the Mongrel::HttpResponse
|
34
|
+
# needs.
|
35
|
+
class CampingHandler < Mongrel::HttpHandler
|
36
|
+
attr_reader :files
|
37
|
+
attr_reader :guard
|
38
|
+
@@file_only_methods = ["GET","HEAD"]
|
39
|
+
|
40
|
+
def initialize(klass)
|
41
|
+
@files = Mongrel::DirHandler.new(nil, false)
|
42
|
+
@guard = Mutex.new
|
43
|
+
@klass = klass
|
44
|
+
end
|
45
|
+
|
46
|
+
def process(request, response)
|
47
|
+
if response.socket.closed?
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
controller = nil
|
52
|
+
@guard.synchronize {
|
53
|
+
controller = @klass.run(request.body, request.params)
|
54
|
+
}
|
55
|
+
|
56
|
+
sendfile, clength = nil
|
57
|
+
response.status = controller.status
|
58
|
+
controller.headers.each do |k, v|
|
59
|
+
if k =~ /^X-SENDFILE$/i
|
60
|
+
sendfile = v
|
61
|
+
elsif k =~ /^CONTENT-LENGTH$/i
|
62
|
+
clength = v.to_i
|
63
|
+
else
|
64
|
+
[*v].each do |vi|
|
65
|
+
response.header[k] = vi
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if sendfile
|
71
|
+
request.params[Mongrel::Const::PATH_INFO] = sendfile
|
72
|
+
@files.process(request, response)
|
73
|
+
elsif controller.body.respond_to? :read
|
74
|
+
response.send_status(clength)
|
75
|
+
response.send_header
|
76
|
+
while chunk = controller.body.read(16384)
|
77
|
+
response.write(chunk)
|
78
|
+
end
|
79
|
+
if controller.body.respond_to? :close
|
80
|
+
controller.body.close
|
81
|
+
end
|
82
|
+
else
|
83
|
+
body = controller.body.to_s
|
84
|
+
response.send_status(body.length)
|
85
|
+
response.send_header
|
86
|
+
response.write(body)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# This is a convenience method that wires up a CampingHandler
|
92
|
+
# for your application on a given port and uri. It's pretty
|
93
|
+
# much all you need for a camping application to work right.
|
94
|
+
#
|
95
|
+
# It returns the Mongrel::HttpServer which you should either
|
96
|
+
# join or somehow manage. The thread is running when
|
97
|
+
# returned.
|
98
|
+
|
99
|
+
def Camping.start(server, port, uri, klass)
|
100
|
+
h = Mongrel::HttpServer.new(server, port)
|
101
|
+
h.register(uri, CampingHandler.new(klass))
|
102
|
+
h.register("/favicon.ico", Mongrel::Error404Handler.new(""))
|
103
|
+
h.run
|
104
|
+
return h
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|