mongrel 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. data/README +4 -3
  2. data/Rakefile +7 -5
  3. data/bin/mongrel_rails +5 -4
  4. data/bin/mongrel_rails_service +237 -0
  5. data/bin/mongrel_rails_svc +194 -0
  6. data/doc/rdoc/classes/Mongrel.html +1 -4
  7. data/doc/rdoc/classes/Mongrel/Const.html +13 -3
  8. data/doc/rdoc/classes/Mongrel/HeaderOut.html +10 -10
  9. data/doc/rdoc/classes/Mongrel/HeaderOut.src/{M000033.html → M000028.html} +4 -4
  10. data/doc/rdoc/classes/Mongrel/HeaderOut.src/{M000034.html → M000029.html} +7 -7
  11. data/doc/rdoc/classes/Mongrel/HttpRequest.html +5 -5
  12. data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000041.html +39 -0
  13. data/doc/rdoc/classes/Mongrel/HttpResponse.html +36 -36
  14. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000034.html +21 -0
  15. data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000041.html → M000035.html} +6 -6
  16. data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000042.html → M000036.html} +5 -5
  17. data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000043.html → M000037.html} +5 -5
  18. data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000044.html → M000038.html} +6 -6
  19. data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000045.html → M000039.html} +6 -6
  20. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000040.html +7 -8
  21. data/doc/rdoc/classes/Mongrel/HttpServer.html +47 -28
  22. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000022.html +33 -0
  23. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000023.html +57 -0
  24. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000024.html +46 -0
  25. data/doc/rdoc/classes/Mongrel/HttpServer.src/{M000031.html → M000025.html} +4 -4
  26. data/doc/rdoc/classes/Mongrel/HttpServer.src/{M000032.html → M000026.html} +4 -4
  27. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000027.html +19 -0
  28. data/doc/rdoc/classes/Mongrel/{HttpHandler.html → StopServer.html} +4 -33
  29. data/doc/rdoc/classes/Mongrel/URIClassifier.html +22 -23
  30. data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000035.html → M000030.html} +0 -0
  31. data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000036.html → M000031.html} +0 -0
  32. data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000037.html → M000032.html} +0 -0
  33. data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000038.html → M000033.html} +0 -0
  34. data/doc/rdoc/created.rid +1 -1
  35. data/doc/rdoc/files/README.html +9 -7
  36. data/doc/rdoc/files/lib/mongrel_rb.html +3 -3
  37. data/doc/rdoc/fr_class_index.html +1 -4
  38. data/doc/rdoc/fr_method_index.html +20 -37
  39. data/examples/mongrel_simple_ctrl.rb +92 -0
  40. data/examples/mongrel_simple_service.rb +116 -0
  41. data/examples/simpletest.rb +3 -1
  42. data/ext/http11/http11_parser.c +176 -263
  43. data/lib/mongrel.rb +56 -343
  44. data/lib/mongrel/cgi.rb +147 -0
  45. data/lib/mongrel/handlers.rb +180 -0
  46. metadata +28 -46
  47. data/doc/rdoc/classes/Mongrel/CGIWrapper.html +0 -346
  48. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000047.html +0 -24
  49. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000048.html +0 -48
  50. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000049.html +0 -34
  51. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000050.html +0 -27
  52. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000051.html +0 -26
  53. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000052.html +0 -18
  54. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000053.html +0 -18
  55. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000054.html +0 -18
  56. data/doc/rdoc/classes/Mongrel/CGIWrapper.src/M000055.html +0 -19
  57. data/doc/rdoc/classes/Mongrel/DirHandler.html +0 -283
  58. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000022.html +0 -20
  59. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000023.html +0 -42
  60. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000024.html +0 -40
  61. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000025.html +0 -31
  62. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000026.html +0 -38
  63. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000027.html +0 -18
  64. data/doc/rdoc/classes/Mongrel/Error404Handler.html +0 -171
  65. data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000056.html +0 -18
  66. data/doc/rdoc/classes/Mongrel/Error404Handler.src/M000057.html +0 -18
  67. data/doc/rdoc/classes/Mongrel/HttpHandler.src/M000039.html +0 -17
  68. data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000058.html +0 -31
  69. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000046.html +0 -20
  70. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000028.html +0 -38
  71. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000029.html +0 -64
  72. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000030.html +0 -23
@@ -2,15 +2,18 @@ require 'socket'
2
2
  require 'http11'
3
3
  require 'thread'
4
4
  require 'stringio'
5
- require 'timeout'
6
- require 'cgi'
7
-
5
+ require 'mongrel/cgi'
6
+ require 'mongrel/handlers'
8
7
 
9
8
  # Mongrel module containing all of the classes (include C extensions) for running
10
9
  # a Mongrel web server. It contains a minimalist HTTP server with just enough
11
10
  # functionality to service web application requests fast as possible.
12
11
  module Mongrel
13
12
 
13
+ # Used to stop the HttpServer via Thread.raise.
14
+ class StopServer < Exception
15
+ end
16
+
14
17
  # Every standard HTTP code mapped to the appropriate message. These are
15
18
  # used so frequently that they are placed directly in Mongrel for easy
16
19
  # access rather than Mongrel::Const.
@@ -103,6 +106,9 @@ module Mongrel
103
106
  # The port of our server as given by the HttpServer.new(host,port) call.
104
107
  SERVER_PORT='SERVER_PORT'
105
108
 
109
+ # SERVER_NAME and SERVER_PORT come from this.
110
+ HTTP_HOST='HTTP_HOST'
111
+
106
112
  # Official server protocol key in the HttpRequest parameters.
107
113
  SERVER_PROTOCOL='SERVER_PROTOCOL'
108
114
  # Mongrel claims to support HTTP/1.1.
@@ -112,7 +118,7 @@ module Mongrel
112
118
  SERVER_SOFTWARE='SERVER_SOFTWARE'
113
119
 
114
120
  # Current Mongrel version (used for SERVER_SOFTWARE and other response headers).
115
- MONGREL_VERSION='Mongrel 0.3.5'
121
+ MONGREL_VERSION='Mongrel 0.3.6'
116
122
 
117
123
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
118
124
  ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: #{MONGREL_VERSION}\r\n\r\nNOT FOUND"
@@ -149,7 +155,15 @@ module Mongrel
149
155
 
150
156
  # fix up the CGI requirements
151
157
  params[Const::CONTENT_LENGTH] = params[Const::HTTP_CONTENT_LENGTH] || 0
152
- params[Const::CONTENT_TYPE] ||= params[Const::HTTP_CONTENT_TYPE]
158
+ params[Const::CONTENT_TYPE] = params[Const::HTTP_CONTENT_TYPE] if params[Const::HTTP_CONTENT_TYPE]
159
+ params[Const::GATEWAY_INTERFACE]=Const::GATEWAY_INTERFACE_VALUE
160
+ params[Const::REMOTE_ADDR]=socket.peeraddr[3]
161
+ host,port = params[Const::HTTP_HOST].split(":")
162
+ params[Const::SERVER_NAME]=host
163
+ params[Const::SERVER_PORT]=port if port
164
+ params[Const::SERVER_PROTOCOL]=Const::SERVER_PROTOCOL_VALUE
165
+ params[Const::SERVER_SOFTWARE]=Const::MONGREL_VERSION
166
+
153
167
 
154
168
  # now, if the initial_body isn't long enough for the content length we have to fill it
155
169
  # TODO: adapt for big ass stuff by writing to a temp file
@@ -271,16 +285,6 @@ module Mongrel
271
285
  end
272
286
 
273
287
 
274
- # You implement your application handler with this. It's very light giving
275
- # just the minimum necessary for you to handle a request and shoot back
276
- # a response. Look at the HttpRequest and HttpResponse objects for how
277
- # to use them.
278
- class HttpHandler
279
- def process(request, response)
280
- end
281
- end
282
-
283
-
284
288
  # This is the main driver of Mongrel, while the Mognrel::HttpParser and Mongrel::URIClassifier
285
289
  # make up the majority of how the server functions. It's a very simple class that just
286
290
  # has a thread accepting connections and a simple HttpServer.process_client function
@@ -324,18 +328,13 @@ module Mongrel
324
328
  @req_queue = Queue.new
325
329
  @host = host
326
330
  @port = port
327
- @num_processors = num_processors
331
+ @processors = []
328
332
  @timeout = timeout
329
333
 
330
- @num_processors.times {|i| Thread.new do
334
+ num_processors.times {|i|
335
+ @processors << Thread.new do
331
336
  while client = @req_queue.deq
332
- begin
333
- Timeout.timeout(@timeout) do
334
- process_client(client)
335
- end
336
- rescue Timeout::Error
337
- STDERR.puts "WARNING: Request took longer than #@timeout second timeout"
338
- end
337
+ process_client(client)
339
338
  end
340
339
  end
341
340
  }
@@ -361,21 +360,14 @@ module Mongrel
361
360
  if handler
362
361
  params[Const::PATH_INFO] = path_info
363
362
  params[Const::SCRIPT_NAME] = script_name
364
- params[Const::GATEWAY_INTERFACE]=Const::GATEWAY_INTERFACE_VALUE
365
- params[Const::REMOTE_ADDR]=client.peeraddr[3]
366
- params[Const::SERVER_NAME]=@host
367
- params[Const::SERVER_PORT]=@port
368
- params[Const::SERVER_PROTOCOL]=Const::SERVER_PROTOCOL_VALUE
369
- params[Const::SERVER_SOFTWARE]=Const::MONGREL_VERSION
370
-
371
363
  request = HttpRequest.new(params, data[nread ... data.length], client)
372
364
  response = HttpResponse.new(client)
373
365
  handler.process(request, response)
374
366
  else
375
367
  client.write(Const::ERROR_404_RESPONSE)
376
368
  end
377
-
378
- break
369
+
370
+ break #done
379
371
  else
380
372
  # gotta stream and read again until we can get the parser to be character safe
381
373
  # TODO: make this more efficient since this means we're parsing a lot repeatedly
@@ -402,10 +394,33 @@ module Mongrel
402
394
  def run
403
395
  BasicSocket.do_not_reverse_lookup=true
404
396
  @acceptor = Thread.new do
405
- while true
406
- @req_queue << @socket.accept
397
+ Thread.current[:stopped] = false
398
+
399
+ while not Thread.current[:stopped]
400
+ begin
401
+ @req_queue << @socket.accept
402
+ rescue StopServer
403
+ STDERR.puts "Server stopped. Exiting."
404
+ @socket.close if not @socket.closed?
405
+ break
406
+ rescue Errno::EMFILE
407
+ STDERR.puts "Too many open files. Try increasing ulimits."
408
+ sleep 0.5
409
+ end
410
+ end
411
+
412
+ # now that processing is done we feed enough false onto the request queue to get
413
+ # each processor to exit and stop processing.
414
+ @processors.length.times { @req_queue << false }
415
+
416
+ # finally we wait until the queue is empty
417
+ while @req_queue.length > 0
418
+ STDERR.puts "Shutdown waiting for #{@req_queue.length} requests" if @req_queue.length > 0
419
+ sleep 1
407
420
  end
408
421
  end
422
+
423
+ @acceptor.priority = 1
409
424
  end
410
425
 
411
426
 
@@ -421,318 +436,16 @@ module Mongrel
421
436
  def unregister(uri)
422
437
  @classifier.unregister(uri)
423
438
  end
424
- end
425
-
426
-
427
- # The server normally returns a 404 response if a URI is requested, but it
428
- # also returns a lame empty message. This lets you do a 404 response
429
- # with a custom message for special URIs.
430
- class Error404Handler < HttpHandler
431
-
432
- # Sets the message to return. This is constructed once for the handler
433
- # so it's pretty efficient.
434
- def initialize(msg)
435
- @response = Const::ERROR_404_RESPONSE + msg
436
- end
437
-
438
- # Just kicks back the standard 404 response with your special message.
439
- def process(request, response)
440
- response.socket.write(@response)
441
- end
442
-
443
- end
444
-
445
-
446
- # Serves the contents of a directory. You give it the path to the root
447
- # where the files are located, and it tries to find the files based on
448
- # the PATH_INFO inside the directory. If the requested path is a
449
- # directory then it returns a simple directory listing.
450
- #
451
- # It does a simple protection against going outside it's root path by
452
- # converting all paths to an absolute expanded path, and then making sure
453
- # that the final expanded path includes the root path. If it doesn't
454
- # than it simply gives a 404.
455
- class DirHandler < HttpHandler
456
- MIME_TYPES = {
457
- ".css" => "text/css",
458
- ".gif" => "image/gif",
459
- ".htm" => "text/html",
460
- ".html" => "text/html",
461
- ".jpeg" => "image/jpeg",
462
- ".jpg" => "image/jpeg",
463
- ".js" => "text/javascript",
464
- ".png" => "image/png",
465
- ".swf" => "application/x-shockwave-flash",
466
- ".txt" => "text/plain"
467
- }
468
-
469
-
470
- attr_reader :path
471
-
472
- # You give it the path to the directory root and an (optional)
473
- def initialize(path, listing_allowed=true, index_html="index.html")
474
- @path = File.expand_path(path)
475
- @listing_allowed=listing_allowed
476
- @index_html = index_html
477
- end
478
-
479
- # Checks if the given path can be served and returns the full path (or nil if not).
480
- def can_serve(path_info)
481
- req = File.expand_path(File.join(@path,path_info), @path)
482
-
483
- if req.index(@path) == 0 and File.exist? req
484
- # it exists and it's in the right location
485
- if File.directory? req
486
- # the request is for a directory
487
- index = File.join(req, @index_html)
488
- if File.exist? index
489
- # serve the index
490
- return index
491
- elsif @listing_allowed
492
- # serve the directory
493
- req
494
- else
495
- # do not serve anything
496
- return nil
497
- end
498
- else
499
- # it's a file and it's there
500
- return req
501
- end
502
- else
503
- # does not exist or isn't in the right spot
504
- return nil
505
- end
506
- end
507
-
508
-
509
- # Returns a simplistic directory listing if they're enabled, otherwise a 403.
510
- # Base is the base URI from the REQUEST_URI, dir is the directory to serve
511
- # on the file system (comes from can_serve()), and response is the HttpResponse
512
- # object to send the results on.
513
- def send_dir_listing(base, dir, response)
514
- # take off any trailing / so the links come out right
515
- base.chop! if base[-1] == "/"[-1]
516
-
517
- if @listing_allowed
518
- response.start(200) do |head,out|
519
- head['Content-Type'] = "text/html"
520
- out << "<html><head><title>Directory Listing</title></head><body>"
521
- Dir.entries(dir).each do |child|
522
- next if child == "."
523
-
524
- if child == ".."
525
- out << "<a href=\"#{base}/#{child}\">Up to parent..</a><br/>"
526
- else
527
- out << "<a href=\"#{base}/#{child}\">#{child}</a><br/>"
528
- end
529
- end
530
- out << "</body></html>"
531
- end
532
- else
533
- response.start(403) do |head,out|
534
- out.write("Directory listings not allowed")
535
- end
536
- end
537
- end
538
-
539
-
540
- # Sends the contents of a file back to the user. Not terribly efficient since it's
541
- # opening and closing the file for each read.
542
- def send_file(req, response)
543
- response.start(200) do |head,out|
544
- # set the mime type from our map based on the ending
545
- dot_at = req.rindex(".")
546
- if dot_at
547
- ext = req[dot_at .. -1]
548
- if MIME_TYPES[ext]
549
- head['Content-Type'] = MIME_TYPES[ext]
550
- end
551
- end
552
-
553
- open(req, "rb") do |f|
554
- out.write(f.read)
555
- end
556
- end
557
- end
558
-
559
-
560
- # Process the request to either serve a file or a directory listing
561
- # if allowed (based on the listing_allowed paramter to the constructor).
562
- def process(request, response)
563
- req = can_serve request.params['PATH_INFO']
564
- if not req
565
- # not found, return a 404
566
- response.start(404) do |head,out|
567
- out << "File not found"
568
- end
569
- else
570
- begin
571
- if File.directory? req
572
- send_dir_listing(request.params["REQUEST_URI"],req, response)
573
- else
574
- send_file(req, response)
575
- end
576
- rescue => details
577
- response.reset
578
- response.start(403) do |head,out|
579
- out << "Error accessing file: #{details}"
580
- out << details.backtrace.join("\n")
581
- end
582
- end
583
- end
584
- end
585
439
 
586
- # There is a small number of default mime types for extensions, but
587
- # this lets you add any others you'll need when serving content.
588
- def DirHandler::add_mime_type(extension, type)
589
- MIME_TYPES[extension] = type
440
+ # Stops the acceptor thread and then causes the worker threads to finish
441
+ # off the request queue before finally exiting.
442
+ def stop
443
+ @acceptor[:stopped] = true
444
+ @acceptor.raise(StopServer.new)
590
445
  end
591
446
 
592
447
  end
593
448
 
449
+ end
594
450
 
595
- # The beginning of a complete wrapper around Mongrel's internal HTTP processing
596
- # system but maintaining the original Ruby CGI module. Use this only as a crutch
597
- # to get existing CGI based systems working. It should handle everything, but please
598
- # notify me if you see special warnings. This work is still very alpha so I need
599
- # testers to help work out the various corner cases.
600
- class CGIWrapper < ::CGI
601
- public :env_table
602
- attr_reader :options
603
-
604
- # these are stripped out of any keys passed to CGIWrapper.header function
605
- REMOVED_KEYS = [ "nph","status","server","connection","type",
606
- "charset","length","language","expires"]
607
-
608
- def initialize(request, response, *args)
609
- @request = request
610
- @response = response
611
- @args = *args
612
- @input = StringIO.new(request.body)
613
- @head = {}
614
- @out_called = false
615
- super(*args)
616
- end
617
-
618
- # The header is typically called to send back the header. In our case we
619
- # collect it into a hash for later usage.
620
- #
621
- # nph -- Mostly ignored. It'll output the date.
622
- # connection -- Completely ignored. Why is CGI doing this?
623
- # length -- Ignored since Mongrel figures this out from what you write to output.
624
- #
625
- def header(options = "text/html")
626
-
627
- # if they pass in a string then just write the Content-Type
628
- if options.class == String
629
- @head['Content-Type'] = options
630
- else
631
- # convert the given options into what Mongrel wants
632
- @head['Content-Type'] = options['type'] || "text/html"
633
- @head['Content-Type'] += "; charset=" + options['charset'] if options.has_key? "charset" if options['charset']
634
-
635
- # setup date only if they use nph
636
- @head['Date'] = CGI::rfc1123_date(Time.now) if options['nph']
637
-
638
- # setup the server to use the default or what they set
639
- @head['Server'] = options['server'] || env_table['SERVER_SOFTWARE']
640
-
641
- # remaining possible options they can give
642
- @head['Status'] = options['status'] if options['status']
643
- @head['Content-Language'] = options['language'] if options['language']
644
- @head['Expires'] = options['expires'] if options['expires']
645
-
646
- # drop the keys we don't want anymore
647
- REMOVED_KEYS.each {|k| options.delete(k) }
648
-
649
- # finally just convert the rest raw (which puts 'cookie' directly)
650
- # 'cookie' is translated later as we write the header out
651
- options.each{|k,v| @head[k] = v}
652
- end
653
-
654
- # doing this fakes out the cgi library to think the headers are empty
655
- # we then do the real headers in the out function call later
656
- ""
657
- end
658
-
659
- # Takes any 'cookie' setting and sends it over the Mongrel header,
660
- # then removes the setting from the options. If cookie is an
661
- # Array or Hash then it sends those on with .to_s, otherwise
662
- # it just calls .to_s on it and hopefully your "cookie" can
663
- # write itself correctly.
664
- def send_cookies(to)
665
- # convert the cookies based on the myriad of possible ways to set a cookie
666
- if @head['cookie']
667
- cookie = @head['cookie']
668
- case cookie
669
- when Array
670
- cookie.each {|c| to['Set-Cookie'] = c.to_s }
671
- when Hash
672
- cookie.each_value {|c| to['Set-Cookie'] = c.to_s}
673
- else
674
- to['Set-Cookie'] = options['cookie'].to_s
675
- end
676
-
677
- @head.delete('cookie')
678
-
679
- # @output_cookies seems to never be used, but we'll process it just in case
680
- @output_cookies.each {|c| to['Set-Cookie'] = c.to_s } if @output_cookies
681
- end
682
- end
683
-
684
- # The dumb thing is people can call header or this or both and in any order.
685
- # So, we just reuse header and then finalize the HttpResponse the right way.
686
- # Status is taken from the various options and converted to what Mongrel needs
687
- # via the CGIWrapper.status function.
688
- def out(options = "text/html")
689
- return if @out_called # don't do this more than once
690
-
691
- header(options)
692
-
693
- @response.start status do |head, out|
694
- send_cookies(head)
695
-
696
- @head.each {|k,v| head[k] = v}
697
- out.write(yield || "")
698
- end
699
- end
700
-
701
- # Computes the status once, but lazily so that people who call header twice
702
- # don't get penalized. Because CGI insists on including the options status
703
- # message in the status we have to do a bit of parsing.
704
- def status
705
- if not @status
706
- @status = @head["Status"] || @head["status"]
707
-
708
- if @status
709
- @status[0 ... @status.index(' ')] || "200"
710
- else
711
- @status = "200"
712
- end
713
- end
714
- end
715
-
716
- # Used to wrap the normal args variable used inside CGI.
717
- def args
718
- @args
719
- end
720
-
721
- # Used to wrap the normal env_table variable used inside CGI.
722
- def env_table
723
- @request.params
724
- end
725
-
726
- # Used to wrap the normal stdinput variable used inside CGI.
727
- def stdinput
728
- @input
729
- end
730
-
731
- # The stdoutput should be completely bypassed but we'll drop a warning just in case
732
- def stdoutput
733
- STDERR.puts "WARNING: Your program is doing something not expected. Please tell Zed that stdoutput was used and what software you are running. Thanks."
734
- @response.body
735
- end
736
- end
737
451
 
738
- end