mongrel 1.0.5 → 1.1

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.

@@ -1,521 +1,47 @@
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
1
 
7
2
  require 'socket'
8
- require 'http11'
9
3
  require 'tempfile'
4
+ require 'yaml'
5
+ require 'time'
6
+ require 'etc'
7
+ require 'uri'
8
+ require 'stringio'
10
9
 
11
- begin
12
- require 'fastthread'
13
- rescue RuntimeError => e
14
- warn "fastthread not loaded: #{ e.message }"
15
- rescue LoadError
16
- ensure
17
- require 'thread'
18
- end
10
+ require 'mongrel/gems'
19
11
 
20
- require 'cgi_multipart_eof_fix'
12
+ Mongrel::Gems.require 'cgi_multipart_eof_fix'
13
+ Mongrel::Gems.require 'fastthread'
14
+ require 'thread'
21
15
 
22
- require 'stringio'
16
+ require 'http11'
23
17
  require 'mongrel/cgi'
24
18
  require 'mongrel/handlers'
25
19
  require 'mongrel/command'
26
20
  require 'mongrel/tcphack'
27
- require 'yaml'
28
21
  require 'mongrel/configurator'
29
- require 'time'
30
- require 'etc'
31
- require 'uri'
32
-
22
+ require 'mongrel/uri_classifier'
23
+ require 'mongrel/const'
24
+ require 'mongrel/http_request'
25
+ require 'mongrel/header_out'
26
+ require 'mongrel/http_response'
33
27
 
34
28
  # Mongrel module containing all of the classes (include C extensions) for running
35
29
  # a Mongrel web server. It contains a minimalist HTTP server with just enough
36
30
  # functionality to service web application requests fast as possible.
37
31
  module Mongrel
38
32
 
39
- class URIClassifier
40
- attr_reader :handler_map
41
-
42
- # Returns the URIs that have been registered with this classifier so far.
43
- # The URIs returned should not be modified as this will cause a memory leak.
44
- # You can use this to inspect the contents of the URIClassifier.
45
- def uris
46
- @handler_map.keys
47
- end
48
- # Simply does an inspect that looks like a Hash inspect.
49
- def inspect
50
- @handler_map.inspect
51
- end
52
-
53
- end
54
-
55
33
  # Used to stop the HttpServer via Thread.raise.
56
34
  class StopServer < Exception; end
57
35
 
58
36
  # Thrown at a thread when it is timed out.
59
37
  class TimeoutError < Exception; end
60
38
 
61
- # Every standard HTTP code mapped to the appropriate message. These are
62
- # used so frequently that they are placed directly in Mongrel for easy
63
- # access rather than Mongrel::Const.
64
- HTTP_STATUS_CODES = {
65
- 100 => 'Continue',
66
- 101 => 'Switching Protocols',
67
- 200 => 'OK',
68
- 201 => 'Created',
69
- 202 => 'Accepted',
70
- 203 => 'Non-Authoritative Information',
71
- 204 => 'No Content',
72
- 205 => 'Reset Content',
73
- 206 => 'Partial Content',
74
- 300 => 'Multiple Choices',
75
- 301 => 'Moved Permanently',
76
- 302 => 'Moved Temporarily',
77
- 303 => 'See Other',
78
- 304 => 'Not Modified',
79
- 305 => 'Use Proxy',
80
- 400 => 'Bad Request',
81
- 401 => 'Unauthorized',
82
- 402 => 'Payment Required',
83
- 403 => 'Forbidden',
84
- 404 => 'Not Found',
85
- 405 => 'Method Not Allowed',
86
- 406 => 'Not Acceptable',
87
- 407 => 'Proxy Authentication Required',
88
- 408 => 'Request Time-out',
89
- 409 => 'Conflict',
90
- 410 => 'Gone',
91
- 411 => 'Length Required',
92
- 412 => 'Precondition Failed',
93
- 413 => 'Request Entity Too Large',
94
- 414 => 'Request-URI Too Large',
95
- 415 => 'Unsupported Media Type',
96
- 500 => 'Internal Server Error',
97
- 501 => 'Not Implemented',
98
- 502 => 'Bad Gateway',
99
- 503 => 'Service Unavailable',
100
- 504 => 'Gateway Time-out',
101
- 505 => 'HTTP Version not supported'
102
- }
103
-
104
-
105
- # Frequently used constants when constructing requests or responses. Many times
106
- # the constant just refers to a string with the same contents. Using these constants
107
- # gave about a 3% to 10% performance improvement over using the strings directly.
108
- # Symbols did not really improve things much compared to constants.
109
- #
110
- # While Mongrel does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT,
111
- # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
112
- # too taxing on performance.
113
- module Const
114
- DATE = "Date".freeze
115
-
116
- # This is the part of the path after the SCRIPT_NAME. URIClassifier will determine this.
117
- PATH_INFO="PATH_INFO".freeze
118
-
119
- # This is the initial part that your handler is identified as by URIClassifier.
120
- SCRIPT_NAME="SCRIPT_NAME".freeze
121
-
122
- # The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.
123
- REQUEST_URI='REQUEST_URI'.freeze
124
- REQUEST_PATH='REQUEST_PATH'.freeze
125
-
126
- MONGREL_VERSION="1.0.5".freeze
127
-
128
- MONGREL_TMP_BASE="mongrel".freeze
129
-
130
- # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
131
- ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Mongrel #{MONGREL_VERSION}\r\n\r\nNOT FOUND".freeze
132
-
133
- CONTENT_LENGTH="CONTENT_LENGTH".freeze
134
-
135
- # A common header for indicating the server is too busy. Not used yet.
136
- ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
137
-
138
- # The basic max request size we'll try to read.
139
- CHUNK_SIZE=(16 * 1024)
140
-
141
- # This is the maximum header that is allowed before a client is booted. The parser detects
142
- # this, but we'd also like to do this as well.
143
- MAX_HEADER=1024 * (80 + 32)
144
-
145
- # Maximum request body size before it is moved out of memory and into a tempfile for reading.
146
- MAX_BODY=MAX_HEADER
147
-
148
- # A frozen format for this is about 15% faster
149
- STATUS_FORMAT = "HTTP/1.1 %d %s\r\nConnection: close\r\n".freeze
150
- CONTENT_TYPE = "Content-Type".freeze
151
- LAST_MODIFIED = "Last-Modified".freeze
152
- ETAG = "ETag".freeze
153
- SLASH = "/".freeze
154
- REQUEST_METHOD="REQUEST_METHOD".freeze
155
- GET="GET".freeze
156
- HEAD="HEAD".freeze
157
- # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
158
- ETAG_FORMAT="\"%x-%x-%x\"".freeze
159
- HEADER_FORMAT="%s: %s\r\n".freeze
160
- LINE_END="\r\n".freeze
161
- REMOTE_ADDR="REMOTE_ADDR".freeze
162
- HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze
163
- HTTP_IF_MODIFIED_SINCE="HTTP_IF_MODIFIED_SINCE".freeze
164
- HTTP_IF_NONE_MATCH="HTTP_IF_NONE_MATCH".freeze
165
- REDIRECT = "HTTP/1.1 302 Found\r\nLocation: %s\r\nConnection: close\r\n\r\n".freeze
166
- HOST = "HOST".freeze
167
- end
168
-
169
-
170
- # Basically a Hash with one extra parameter for the HTTP body, mostly used internally.
39
+ # A Hash with one extra parameter for the HTTP body, used internally.
171
40
  class HttpParams < Hash
172
41
  attr_accessor :http_body
173
42
  end
174
43
 
175
44
 
176
- # When a handler is found for a registered URI then this class is constructed
177
- # and passed to your HttpHandler::process method. You should assume that
178
- # *one* handler processes all requests. Included in the HttpRequest is a
179
- # HttpRequest.params Hash that matches common CGI params, and a HttpRequest.body
180
- # which is a string containing the request body (raw for now).
181
- #
182
- # The HttpRequest.initialize method will convert any request that is larger than
183
- # Const::MAX_BODY into a Tempfile and use that as the body. Otherwise it uses
184
- # a StringIO object. To be safe, you should assume it works like a file.
185
- #
186
- # The HttpHandler.request_notify system is implemented by having HttpRequest call
187
- # HttpHandler.request_begins, HttpHandler.request_progress, HttpHandler.process during
188
- # the IO processing. This adds a small amount of overhead but lets you implement
189
- # finer controlled handlers and filters.
190
- class HttpRequest
191
- attr_reader :body, :params
192
-
193
- # You don't really call this. It's made for you.
194
- # Main thing it does is hook up the params, and store any remaining
195
- # body data into the HttpRequest.body attribute.
196
- def initialize(params, socket, dispatchers)
197
- @params = params
198
- @socket = socket
199
- @dispatchers = dispatchers
200
- content_length = @params[Const::CONTENT_LENGTH].to_i
201
- remain = content_length - @params.http_body.length
202
-
203
- # tell all dispatchers the request has begun
204
- @dispatchers.each do |dispatcher|
205
- dispatcher.request_begins(@params)
206
- end unless @dispatchers.nil? || @dispatchers.empty?
207
-
208
- # 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.
209
- if remain <= 0
210
- # we've got everything, pack it up
211
- @body = StringIO.new
212
- @body.write @params.http_body
213
- update_request_progress(0, content_length)
214
- elsif remain > 0
215
- # must read more data to complete body
216
- if remain > Const::MAX_BODY
217
- # huge body, put it in a tempfile
218
- @body = Tempfile.new(Const::MONGREL_TMP_BASE)
219
- @body.binmode
220
- else
221
- # small body, just use that
222
- @body = StringIO.new
223
- end
224
-
225
- @body.write @params.http_body
226
- read_body(remain, content_length)
227
- end
228
-
229
- @body.rewind if @body
230
- end
231
-
232
- # updates all dispatchers about our progress
233
- def update_request_progress(clen, total)
234
- return if @dispatchers.nil? || @dispatchers.empty?
235
- @dispatchers.each do |dispatcher|
236
- dispatcher.request_progress(@params, clen, total)
237
- end
238
- end
239
- private :update_request_progress
240
-
241
- # Does the heavy lifting of properly reading the larger body requests in
242
- # small chunks. It expects @body to be an IO object, @socket to be valid,
243
- # and will set @body = nil if the request fails. It also expects any initial
244
- # part of the body that has been read to be in the @body already.
245
- def read_body(remain, total)
246
- begin
247
- # write the odd sized chunk first
248
- @params.http_body = read_socket(remain % Const::CHUNK_SIZE)
249
-
250
- remain -= @body.write(@params.http_body)
251
-
252
- update_request_progress(remain, total)
253
-
254
- # then stream out nothing but perfectly sized chunks
255
- until remain <= 0 or @socket.closed?
256
- # ASSUME: we are writing to a disk and these writes always write the requested amount
257
- @params.http_body = read_socket(Const::CHUNK_SIZE)
258
- remain -= @body.write(@params.http_body)
259
-
260
- update_request_progress(remain, total)
261
- end
262
- rescue Object => e
263
- STDERR.puts "#{Time.now}: Error reading HTTP body: #{e.inspect}"
264
- STDERR.puts e.backtrace.join("\n")
265
- # any errors means we should delete the file, including if the file is dumped
266
- @socket.close rescue nil
267
- @body.delete if @body.class == Tempfile
268
- @body = nil # signals that there was a problem
269
- end
270
- end
271
-
272
- def read_socket(len)
273
- if !@socket.closed?
274
- data = @socket.read(len)
275
- if !data
276
- raise "Socket read return nil"
277
- elsif data.length != len
278
- raise "Socket read returned insufficient data: #{data.length}"
279
- else
280
- data
281
- end
282
- else
283
- raise "Socket already closed when reading."
284
- end
285
- end
286
-
287
- # Performs URI escaping so that you can construct proper
288
- # query strings faster. Use this rather than the cgi.rb
289
- # version since it's faster. (Stolen from Camping).
290
- def self.escape(s)
291
- s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
292
- '%'+$1.unpack('H2'*$1.size).join('%').upcase
293
- }.tr(' ', '+')
294
- end
295
-
296
-
297
- # Unescapes a URI escaped string. (Stolen from Camping).
298
- def self.unescape(s)
299
- s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
300
- [$1.delete('%')].pack('H*')
301
- }
302
- end
303
-
304
- # Parses a query string by breaking it up at the '&'
305
- # and ';' characters. You can also use this to parse
306
- # cookies by changing the characters used in the second
307
- # parameter (which defaults to '&;'.
308
- def self.query_parse(qs, d = '&;')
309
- params = {}
310
- (qs||'').split(/[#{d}] */n).inject(params) { |h,p|
311
- k, v=unescape(p).split('=',2)
312
- if cur = params[k]
313
- if cur.class == Array
314
- params[k] << v
315
- else
316
- params[k] = [cur, v]
317
- end
318
- else
319
- params[k] = v
320
- end
321
- }
322
-
323
- return params
324
- end
325
- end
326
-
327
-
328
- # This class implements a simple way of constructing the HTTP headers dynamically
329
- # via a Hash syntax. Think of it as a write-only Hash. Refer to HttpResponse for
330
- # information on how this is used.
331
- #
332
- # One consequence of this write-only nature is that you can write multiple headers
333
- # by just doing them twice (which is sometimes needed in HTTP), but that the normal
334
- # semantics for Hash (where doing an insert replaces) is not there.
335
- class HeaderOut
336
- attr_reader :out
337
- attr_accessor :allowed_duplicates
338
-
339
- def initialize(out)
340
- @sent = {}
341
- @allowed_duplicates = {"Set-Cookie" => true, "Set-Cookie2" => true,
342
- "Warning" => true, "WWW-Authenticate" => true}
343
- @out = out
344
- end
345
-
346
- # Simply writes "#{key}: #{value}" to an output buffer.
347
- def[]=(key,value)
348
- if not @sent.has_key?(key) or @allowed_duplicates.has_key?(key)
349
- @sent[key] = true
350
- @out.write(Const::HEADER_FORMAT % [key, value])
351
- end
352
- end
353
- end
354
-
355
-
356
- # Writes and controls your response to the client using the HTTP/1.1 specification.
357
- # You use it by simply doing:
358
- #
359
- # response.start(200) do |head,out|
360
- # head['Content-Type'] = 'text/plain'
361
- # out.write("hello\n")
362
- # end
363
- #
364
- # The parameter to start is the response code--which Mongrel will translate for you
365
- # based on HTTP_STATUS_CODES. The head parameter is how you write custom headers.
366
- # The out parameter is where you write your body. The default status code for
367
- # HttpResponse.start is 200 so the above example is redundant.
368
- #
369
- # As you can see, it's just like using a Hash and as you do this it writes the proper
370
- # header to the output on the fly. You can even intermix specifying headers and
371
- # writing content. The HttpResponse class with write the things in the proper order
372
- # once the HttpResponse.block is ended.
373
- #
374
- # You may also work the HttpResponse object directly using the various attributes available
375
- # for the raw socket, body, header, and status codes. If you do this you're on your own.
376
- # A design decision was made to force the client to not pipeline requests. HTTP/1.1
377
- # pipelining really kills the performance due to how it has to be handled and how
378
- # unclear the standard is. To fix this the HttpResponse gives a "Connection: close"
379
- # header which forces the client to close right away. The bonus for this is that it
380
- # gives a pretty nice speed boost to most clients since they can close their connection
381
- # immediately.
382
- #
383
- # One additional caveat is that you don't have to specify the Content-length header
384
- # as the HttpResponse will write this for you based on the out length.
385
- class HttpResponse
386
- attr_reader :socket
387
- attr_reader :body
388
- attr_writer :body
389
- attr_reader :header
390
- attr_reader :status
391
- attr_writer :status
392
- attr_reader :body_sent
393
- attr_reader :header_sent
394
- attr_reader :status_sent
395
-
396
- def initialize(socket)
397
- @socket = socket
398
- @body = StringIO.new
399
- @status = 404
400
- @reason = HTTP_STATUS_CODES[@status]
401
- @header = HeaderOut.new(StringIO.new)
402
- @header[Const::DATE] = Time.now.httpdate
403
- @body_sent = false
404
- @header_sent = false
405
- @status_sent = false
406
- end
407
-
408
- # Receives a block passing it the header and body for you to work with.
409
- # When the block is finished it writes everything you've done to
410
- # the socket in the proper order. This lets you intermix header and
411
- # body content as needed. Handlers are able to modify pretty much
412
- # any part of the request in the chain, and can stop further processing
413
- # by simple passing "finalize=true" to the start method. By default
414
- # all handlers run and then mongrel finalizes the request when they're
415
- # all done.
416
- def start(status=200, finalize=false, reason=HTTP_STATUS_CODES[status])
417
- @status = status.to_i
418
- @reason = reason
419
- yield @header, @body
420
- finished if finalize
421
- end
422
-
423
- # Primarily used in exception handling to reset the response output in order to write
424
- # an alternative response. It will abort with an exception if you have already
425
- # sent the header or the body. This is pretty catastrophic actually.
426
- def reset
427
- if @body_sent
428
- raise "You have already sent the request body."
429
- elsif @header_sent
430
- raise "You have already sent the request headers."
431
- else
432
- @header.out.truncate(0)
433
- @body.close
434
- @body = StringIO.new
435
- end
436
- end
437
-
438
- def send_status(content_length=@body.length)
439
- if not @status_sent
440
- @header['Content-Length'] = content_length if content_length and @status != 304
441
- write(Const::STATUS_FORMAT % [@status, @reason])
442
- @status_sent = true
443
- end
444
- end
445
-
446
- def send_header
447
- if not @header_sent
448
- @header.out.rewind
449
- write(@header.out.read + Const::LINE_END)
450
- @header_sent = true
451
- end
452
- end
453
-
454
- def send_body
455
- if not @body_sent
456
- @body.rewind
457
- write(@body.read)
458
- @body_sent = true
459
- end
460
- end
461
-
462
- # Appends the contents of +path+ to the response stream. The file is opened for binary
463
- # reading and written in chunks to the socket.
464
- #
465
- # Sendfile API support has been removed in 0.3.13.4 due to stability problems.
466
- def send_file(path, small_file = false)
467
- if small_file
468
- File.open(path, "rb") {|f| @socket << f.read }
469
- else
470
- File.open(path, "rb") do |f|
471
- while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0
472
- begin
473
- write(chunk)
474
- rescue Object => exc
475
- break
476
- end
477
- end
478
- end
479
- end
480
- @body_sent = true
481
- end
482
-
483
- def socket_error(details)
484
- # ignore these since it means the client closed off early
485
- @socket.close rescue nil
486
- done = true
487
- raise details
488
- end
489
-
490
- def write(data)
491
- @socket.write(data)
492
- rescue => details
493
- socket_error(details)
494
- end
495
-
496
- # This takes whatever has been done to header and body and then writes it in the
497
- # proper format to make an HTTP/1.1 response.
498
- def finished
499
- send_status
500
- send_header
501
- send_body
502
- end
503
-
504
- # Used during error conditions to mark the response as "done" so there isn't any more processing
505
- # sent to the client.
506
- def done=(val)
507
- @status_sent = true
508
- @header_sent = true
509
- @body_sent = true
510
- end
511
-
512
- def done
513
- (@status_sent and @header_sent and @body_sent)
514
- end
515
-
516
- end
517
-
518
-
519
45
  # This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier
520
46
  # make up the majority of how the server functions. It's a very simple class that just
521
47
  # has a thread accepting connections and a simple HttpServer.process_client function
@@ -558,7 +84,10 @@ module Mongrel
558
84
  # socket.accept calls in order to give the server a cheap throttle time. It defaults to 0 and
559
85
  # actually if it is 0 then the sleep is not done at all.
560
86
  def initialize(host, port, num_processors=950, throttle=0, timeout=60)
87
+
88
+ tries = 0
561
89
  @socket = TCPServer.new(host, port)
90
+
562
91
  @classifier = URIClassifier.new
563
92
  @host = host
564
93
  @port = port
@@ -680,12 +209,12 @@ module Mongrel
680
209
  STDERR.puts "#{Time.now}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'"
681
210
  error_msg = "Mongrel timed out this thread: #{reason}"
682
211
  mark = Time.now
683
- @workers.list.each do |w|
684
- w[:started_on] = Time.now if not w[:started_on]
212
+ @workers.list.each do |worker|
213
+ worker[:started_on] = Time.now if not worker[:started_on]
685
214
 
686
- if mark - w[:started_on] > @timeout + @throttle
215
+ if mark - worker[:started_on] > @timeout + @throttle
687
216
  STDERR.puts "Thread #{w.inspect} is too old, killing."
688
- w.raise(TimeoutError.new(error_msg))
217
+ worker.raise(TimeoutError.new(error_msg))
689
218
  end
690
219
  end
691
220
  end
@@ -699,7 +228,7 @@ module Mongrel
699
228
  # that much longer.
700
229
  def graceful_shutdown
701
230
  while reap_dead_workers("shutdown") > 0
702
- STDERR.print "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout + @throttle} seconds."
231
+ STDERR.puts "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout + @throttle} seconds."
703
232
  sleep @timeout / 10
704
233
  end
705
234
  end
@@ -720,7 +249,7 @@ module Mongrel
720
249
  end
721
250
  end
722
251
  end
723
-
252
+
724
253
  # Runs the thing. It returns the thread used so you can "join" it. You can also
725
254
  # access the HttpServer::acceptor attribute to get the thread later.
726
255
  def run
@@ -733,42 +262,46 @@ module Mongrel
733
262
  end
734
263
 
735
264
  @acceptor = Thread.new do
736
- while true
737
- begin
738
- client = @socket.accept
739
-
740
- if defined?($tcp_cork_opts) and $tcp_cork_opts
741
- client.setsockopt(*$tcp_cork_opts) rescue nil
742
- end
743
-
744
- worker_list = @workers.list
745
-
746
- if worker_list.length >= @num_processors
747
- STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection."
265
+ begin
266
+ while true
267
+ begin
268
+ client = @socket.accept
269
+
270
+ if defined?($tcp_cork_opts) and $tcp_cork_opts
271
+ client.setsockopt(*$tcp_cork_opts) rescue nil
272
+ end
273
+
274
+ worker_list = @workers.list
275
+
276
+ if worker_list.length >= @num_processors
277
+ STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection."
278
+ client.close rescue nil
279
+ reap_dead_workers("max processors")
280
+ else
281
+ thread = Thread.new(client) {|c| process_client(c) }
282
+ thread[:started_on] = Time.now
283
+ @workers.add(thread)
284
+
285
+ sleep @throttle/100.0 if @throttle > 0
286
+ end
287
+ rescue StopServer
288
+ break
289
+ rescue Errno::EMFILE
290
+ reap_dead_workers("too many open files")
291
+ sleep 0.5
292
+ rescue Errno::ECONNABORTED
293
+ # client closed the socket even before accept
748
294
  client.close rescue nil
749
- reap_dead_workers("max processors")
750
- else
751
- thread = Thread.new(client) {|c| process_client(c) }
752
- thread[:started_on] = Time.now
753
- @workers.add(thread)
754
-
755
- sleep @throttle/100.0 if @throttle > 0
295
+ rescue Object => e
296
+ STDERR.puts "#{Time.now}: Unhandled listen loop exception #{e.inspect}."
297
+ STDERR.puts e.backtrace.join("\n")
756
298
  end
757
- rescue StopServer
758
- @socket.close
759
- break
760
- rescue Errno::EMFILE
761
- reap_dead_workers("too many open files")
762
- sleep 0.5
763
- rescue Errno::ECONNABORTED
764
- # client closed the socket even before accept
765
- client.close rescue nil
766
- rescue Object => e
767
- STDERR.puts "#{Time.now}: Unhandled listen loop exception #{e.inspect}."
768
- STDERR.puts e.backtrace.join("\n")
769
299
  end
300
+ graceful_shutdown
301
+ ensure
302
+ @socket.close
303
+ # STDERR.puts "#{Time.now}: Closed socket."
770
304
  end
771
- graceful_shutdown
772
305
  end
773
306
 
774
307
  return @acceptor
@@ -778,25 +311,16 @@ module Mongrel
778
311
  # found in the prefix of a request then your handler's HttpHandler::process method
779
312
  # is called. See Mongrel::URIClassifier#register for more information.
780
313
  #
781
- # If you set in_front=true then the passed in handler will be put in front in the list.
782
- # Otherwise it's placed at the end of the list.
314
+ # If you set in_front=true then the passed in handler will be put in the front of the list
315
+ # for that particular URI. Otherwise it's placed at the end of the list.
783
316
  def register(uri, handler, in_front=false)
784
- script_name, path_info, handlers = @classifier.resolve(uri)
785
-
786
- if not handlers
317
+ begin
787
318
  @classifier.register(uri, [handler])
788
- else
789
- if path_info.length == 0 or (script_name == Const::SLASH and path_info == Const::SLASH)
790
- if in_front
791
- handlers.unshift(handler)
792
- else
793
- handlers << handler
794
- end
795
- else
796
- @classifier.register(uri, [handler])
797
- end
319
+ rescue URIClassifier::RegistrationError
320
+ handlers = @classifier.resolve(uri)[2]
321
+ method_name = in_front ? 'unshift' : 'push'
322
+ handlers.send(method_name, handler)
798
323
  end
799
-
800
324
  handler.listener = self
801
325
  end
802
326
 
@@ -812,10 +336,16 @@ module Mongrel
812
336
  def stop(synchronous=false)
813
337
  @acceptor.raise(StopServer.new)
814
338
 
815
- if synchronous
339
+ if synchronous
816
340
  sleep(0.5) while @acceptor.alive?
817
341
  end
818
342
  end
819
343
 
820
344
  end
821
345
  end
346
+
347
+ # Load experimental library, if present. We put it here so it can override anything
348
+ # in regular Mongrel.
349
+
350
+ $LOAD_PATH.unshift 'projects/mongrel_experimental/lib/'
351
+ Mongrel::Gems.require 'mongrel_experimental', ">=#{Mongrel::Const::MONGREL_VERSION}"