Class: Mongrel::HttpServer
- Inherits:
-
Object
- Object
- Mongrel::HttpServer
- Defined in:
- lib/mongrel.rb
Overview
This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier make up the majority of how the server functions. It’s a very simple class that just has a thread accepting connections and a simple HttpServer.process_client function to do the heavy lifting with the IO and Ruby.
You use it by doing the following:
server = HttpServer.new("0.0.0.0", 3000)
server.register("/stuff", MyNiftyHandler.new)
server.run.join
The last line can be just server.run if you don’t want to join the thread used. If you don’t though Ruby will mysteriously just exit on you.
Ruby’s thread implementation is “interesting” to say the least. Experiments with many different types of IO processing simply cannot make a dent in it. Future releases of Mongrel will find other creative ways to make threads faster, but don’t hold your breath until Ruby 1.9 is actually finally useful.
Instance Attribute Summary collapse
-
#acceptor ⇒ Object
readonly
Returns the value of attribute acceptor.
-
#classifier ⇒ Object
readonly
Returns the value of attribute classifier.
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#num_processors ⇒ Object
readonly
Returns the value of attribute num_processors.
-
#port ⇒ Object
readonly
Returns the value of attribute port.
-
#throttle ⇒ Object
readonly
Returns the value of attribute throttle.
-
#timeout ⇒ Object
readonly
Returns the value of attribute timeout.
-
#workers ⇒ Object
readonly
Returns the value of attribute workers.
Instance Method Summary collapse
- #configure_socket_options ⇒ Object
-
#graceful_shutdown ⇒ Object
Performs a wait on all the currently running threads and kills any that take too long.
-
#initialize(host, port, num_processors = 950, throttle = 0, timeout = 60) ⇒ HttpServer
constructor
Creates a working server on host:port (strange things happen if port isn’t a Number).
-
#process_client(client) ⇒ Object
Does the majority of the IO processing.
-
#reap_dead_workers(reason = 'unknown') ⇒ Object
Used internally to kill off any worker threads that have taken too long to complete processing.
-
#register(uri, handler, in_front = false) ⇒ Object
Simply registers a handler with the internal URIClassifier.
-
#run ⇒ Object
Runs the thing.
-
#stop(synchronous = false) ⇒ Object
Stops the acceptor thread and then causes the worker threads to finish off the request queue before finally exiting.
-
#unregister(uri) ⇒ Object
Removes any handlers registered at the given URI.
Constructor Details
#initialize(host, port, num_processors = 950, throttle = 0, timeout = 60) ⇒ HttpServer
Creates a working server on host:port (strange things happen if port isn’t a Number). Use HttpServer::run to start the server and HttpServer.acceptor.join to join the thread that’s processing incoming requests on the socket.
The num_processors optional argument is the maximum number of concurrent processors to accept, anything over this is closed immediately to maintain server processing performance. This may seem mean but it is the most efficient way to deal with overload. Other schemes involve still parsing the client’s request which defeats the point of an overload handling system.
The throttle parameter is a sleep timeout (in hundredths of a second) that is placed between socket.accept calls in order to give the server a cheap throttle time. It defaults to 0 and actually if it is 0 then the sleep is not done at all.
560 561 562 563 564 565 566 567 568 569 |
# File 'lib/mongrel.rb', line 560 def initialize(host, port, num_processors=950, throttle=0, timeout=60) @socket = TCPServer.new(host, port) @classifier = URIClassifier.new @host = host @port = port @workers = ThreadGroup.new @throttle = throttle @num_processors = num_processors @timeout = timeout end |
Instance Attribute Details
#acceptor ⇒ Object (readonly)
Returns the value of attribute acceptor.
538 539 540 |
# File 'lib/mongrel.rb', line 538 def acceptor @acceptor end |
#classifier ⇒ Object (readonly)
Returns the value of attribute classifier.
540 541 542 |
# File 'lib/mongrel.rb', line 540 def classifier @classifier end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
541 542 543 |
# File 'lib/mongrel.rb', line 541 def host @host end |
#num_processors ⇒ Object (readonly)
Returns the value of attribute num_processors.
545 546 547 |
# File 'lib/mongrel.rb', line 545 def num_processors @num_processors end |
#port ⇒ Object (readonly)
Returns the value of attribute port.
542 543 544 |
# File 'lib/mongrel.rb', line 542 def port @port end |
#throttle ⇒ Object (readonly)
Returns the value of attribute throttle.
543 544 545 |
# File 'lib/mongrel.rb', line 543 def throttle @throttle end |
#timeout ⇒ Object (readonly)
Returns the value of attribute timeout.
544 545 546 |
# File 'lib/mongrel.rb', line 544 def timeout @timeout end |
#workers ⇒ Object (readonly)
Returns the value of attribute workers.
539 540 541 |
# File 'lib/mongrel.rb', line 539 def workers @workers end |
Instance Method Details
#configure_socket_options ⇒ Object
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 |
# File 'lib/mongrel.rb', line 707 def case RUBY_PLATFORM when /linux/ # 9 is currently TCP_DEFER_ACCEPT $tcp_defer_accept_opts = [Socket::SOL_TCP, 9, 1] $tcp_cork_opts = [Socket::SOL_TCP, 3, 1] when /freebsd(([1-4]\..{1,2})|5\.[0-4])/ # Do nothing, just closing a bug when freebsd <= 5.4 when /freebsd/ # Use the HTTP accept filter if available. # The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg unless `/sbin/sysctl -nq net.inet.accf.http`.empty? $tcp_defer_accept_opts = [Socket::SOL_SOCKET, Socket::SO_ACCEPTFILTER, ['httpready', nil].pack('a16a240')] end end end |
#graceful_shutdown ⇒ Object
Performs a wait on all the currently running threads and kills any that take too long. It waits by @timeout seconds, which can be set in .initialize or via mongrel_rails. The @throttle setting does extend this waiting period by that much longer.
700 701 702 703 704 705 |
# File 'lib/mongrel.rb', line 700 def graceful_shutdown while reap_dead_workers("shutdown") > 0 STDERR.print "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout + @throttle} seconds." sleep @timeout / 10 end end |
#process_client(client) ⇒ Object
Does the majority of the IO processing. It has been written in Ruby using about 7 different IO processing strategies and no matter how it’s done the performance just does not improve. It is currently carefully constructed to make sure that it gets the best possible performance, but anyone who thinks they can make it faster is more than welcome to take a crack at it.
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 |
# File 'lib/mongrel.rb', line 576 def process_client(client) begin parser = HttpParser.new params = HttpParams.new request = nil data = client.readpartial(Const::CHUNK_SIZE) nparsed = 0 # Assumption: nparsed will always be less since data will get filled with more # after each parsing. If it doesn't get more then there was a problem # with the read operation on the client socket. Effect is to stop processing when the # socket can't fill the buffer for further parsing. while nparsed < data.length nparsed = parser.execute(params, data, nparsed) if parser.finished? if not params[Const::REQUEST_PATH] # it might be a dumbass full host request header uri = URI.parse(params[Const::REQUEST_URI]) params[Const::REQUEST_PATH] = uri.path end raise "No REQUEST PATH" if not params[Const::REQUEST_PATH] script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH]) if handlers params[Const::PATH_INFO] = path_info params[Const::SCRIPT_NAME] = script_name # From http://www.ietf.org/rfc/rfc3875 : # "Script authors should be aware that the REMOTE_ADDR and REMOTE_HOST # meta-variables (see sections 4.1.8 and 4.1.9) may not identify the # ultimate source of the request. They identify the client for the # immediate request to the server; that client may be a proxy, gateway, # or other intermediary acting on behalf of the actual source client." params[Const::REMOTE_ADDR] = client.peeraddr.last # select handlers that want more detailed request notification notifiers = handlers.select { |h| h.request_notify } request = HttpRequest.new(params, client, notifiers) # in the case of large file uploads the user could close the socket, so skip those requests break if request.body == nil # nil signals from HttpRequest::initialize that the request was aborted # request is good so far, continue processing the response response = HttpResponse.new(client) # Process each handler in registered order until we run out or one finalizes the response. handlers.each do |handler| handler.process(request, response) break if response.done or client.closed? end # And finally, if nobody closed the response off, we finalize it. unless response.done or client.closed? response.finished end else # Didn't find it, return a stock 404 response. client.write(Const::ERROR_404_RESPONSE) end break #done else # Parser is not done, queue up more data to read and continue parsing chunk = client.readpartial(Const::CHUNK_SIZE) break if !chunk or chunk.length == 0 # read failed, stop processing data << chunk if data.length >= Const::MAX_HEADER raise HttpParserError.new("HEADER is longer than allowed, aborting client early.") end end end rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF client.close rescue nil rescue HttpParserError => e STDERR.puts "#{Time.now}: HTTP parse error, malformed request (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}" STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n" rescue Errno::EMFILE reap_dead_workers('too many files') rescue Object => e STDERR.puts "#{Time.now}: Read error: #{e.inspect}" STDERR.puts e.backtrace.join("\n") ensure begin client.close rescue IOError # Already closed rescue Object => e STDERR.puts "#{Time.now}: Client error: #{e.inspect}" STDERR.puts e.backtrace.join("\n") end request.body.delete if request and request.body.class == Tempfile end end |
#reap_dead_workers(reason = 'unknown') ⇒ Object
Used internally to kill off any worker threads that have taken too long to complete processing. Only called if there are too many processors currently servicing. It returns the count of workers still active after the reap is done. It only runs if there are workers to reap.
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 |
# File 'lib/mongrel.rb', line 678 def reap_dead_workers(reason='unknown') if @workers.list.length > 0 STDERR.puts "#{Time.now}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'" error_msg = "Mongrel timed out this thread: #{reason}" mark = Time.now @workers.list.each do |w| w[:started_on] = Time.now if not w[:started_on] if mark - w[:started_on] > @timeout + @throttle STDERR.puts "Thread #{w.inspect} is too old, killing." w.raise(TimeoutError.new(error_msg)) end end end return @workers.list.length end |
#register(uri, handler, in_front = false) ⇒ Object
Simply registers a handler with the internal URIClassifier. When the URI is found in the prefix of a request then your handler’s HttpHandler::process method is called. See Mongrel::URIClassifier#register for more information.
If you set in_front=true then the passed in handler will be put in front in the list. Otherwise it’s placed at the end of the list.
783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 |
# File 'lib/mongrel.rb', line 783 def register(uri, handler, in_front=false) script_name, path_info, handlers = @classifier.resolve(uri) if not handlers @classifier.register(uri, [handler]) else if path_info.length == 0 or (script_name == Const::SLASH and path_info == Const::SLASH) if in_front handlers.unshift(handler) else handlers << handler end else @classifier.register(uri, [handler]) end end handler.listener = self end |
#run ⇒ Object
Runs the thing. It returns the thread used so you can “join” it. You can also access the HttpServer::acceptor attribute to get the thread later.
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 |
# File 'lib/mongrel.rb', line 726 def run BasicSocket.do_not_reverse_lookup=true if defined?($tcp_defer_accept_opts) and $tcp_defer_accept_opts @socket.setsockopt(*$tcp_defer_accept_opts) rescue nil end @acceptor = Thread.new do while true begin client = @socket.accept if defined?($tcp_cork_opts) and $tcp_cork_opts client.setsockopt(*$tcp_cork_opts) rescue nil end worker_list = @workers.list if worker_list.length >= @num_processors STDERR.puts "Server overloaded with #{worker_list.length} processors (#@num_processors max). Dropping connection." client.close rescue nil reap_dead_workers("max processors") else thread = Thread.new(client) {|c| process_client(c) } thread[:started_on] = Time.now @workers.add(thread) sleep @throttle/100.0 if @throttle > 0 end rescue StopServer @socket.close break rescue Errno::EMFILE reap_dead_workers("too many open files") sleep 0.5 rescue Errno::ECONNABORTED # client closed the socket even before accept client.close rescue nil rescue Object => e STDERR.puts "#{Time.now}: Unhandled listen loop exception #{e.inspect}." STDERR.puts e.backtrace.join("\n") end end graceful_shutdown end return @acceptor end |
#stop(synchronous = false) ⇒ Object
Stops the acceptor thread and then causes the worker threads to finish off the request queue before finally exiting.
812 813 814 815 816 817 818 |
# File 'lib/mongrel.rb', line 812 def stop(synchronous=false) @acceptor.raise(StopServer.new) if synchronous sleep(0.5) while @acceptor.alive? end end |
#unregister(uri) ⇒ Object
Removes any handlers registered at the given URI. See Mongrel::URIClassifier#unregister for more information. Remember this removes them all so the entire processing chain goes away.
806 807 808 |
# File 'lib/mongrel.rb', line 806 def unregister(uri) @classifier.unregister(uri) end |