Class: Mongrel::HttpServer
- Inherits:
-
Object
- Object
- Mongrel::HttpServer
- Defined in:
- lib/mongrel.rb
Overview
This is the main driver of Mongrel, while the Mognrel::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", MyNifterHandler.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.
-
#port ⇒ Object
readonly
Returns the value of attribute port.
-
#workers ⇒ Object
readonly
Returns the value of attribute workers.
Instance Method Summary collapse
-
#initialize(host, port, num_processors = (2**30-1), timeout = 0) ⇒ 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(worker_list) ⇒ 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 ⇒ 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 = (2**30-1), timeout = 0) ⇒ 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 timeout 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.
386 387 388 389 390 391 392 393 394 395 |
# File 'lib/mongrel.rb', line 386 def initialize(host, port, num_processors=(2**30-1), timeout=0) @socket = TCPServer.new(host, port) @classifier = URIClassifier.new @host = host @port = port @workers = ThreadGroup.new @timeout = timeout @num_processors = num_processors @death_time = 60 end |
Instance Attribute Details
#acceptor ⇒ Object (readonly)
Returns the value of attribute acceptor.
367 368 369 |
# File 'lib/mongrel.rb', line 367 def acceptor @acceptor end |
#classifier ⇒ Object (readonly)
Returns the value of attribute classifier.
369 370 371 |
# File 'lib/mongrel.rb', line 369 def classifier @classifier end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
370 371 372 |
# File 'lib/mongrel.rb', line 370 def host @host end |
#port ⇒ Object (readonly)
Returns the value of attribute port.
371 372 373 |
# File 'lib/mongrel.rb', line 371 def port @port end |
#workers ⇒ Object (readonly)
Returns the value of attribute workers.
368 369 370 |
# File 'lib/mongrel.rb', line 368 def workers @workers end |
Instance Method Details
#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.
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 |
# File 'lib/mongrel.rb', line 403 def process_client(client) begin parser = HttpParser.new params = {} data = client.readpartial(Const::CHUNK_SIZE) while true nread = parser.execute(params, data) if parser.finished? script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_URI]) if handlers params[Const::PATH_INFO] = path_info params[Const::SCRIPT_NAME] = script_name params[Const::REMOTE_ADDR] = params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last request = HttpRequest.new(params, data[nread ... data.length], client) response = HttpResponse.new(client) handlers.each do |handler| handler.process(request, response) break if response.done end if not response.done response.finished end else client.write(Const::ERROR_404_RESPONSE) end break #done else # gotta stream and read again until we can get the parser to be character safe # TODO: make this more efficient since this means we're parsing a lot repeatedly if data.length >= Const::MAX_HEADER raise HttpParserError.new("HEADER is longer than allowed, aborting client early.") end parser.reset data << client.readpartial(Const::CHUNK_SIZE) end end rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL # ignored rescue HttpParserError STDERR.puts "BAD CLIENT (#{client.peeraddr.last}): #$!" STDERR.puts "REQUEST DATA: #{data}" rescue => details STDERR.puts "ERROR: #$!" STDERR.puts details.backtrace.join("\n") ensure client.close end end |
#reap_dead_workers(worker_list) ⇒ 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.
465 466 467 468 469 470 471 472 473 474 475 |
# File 'lib/mongrel.rb', line 465 def reap_dead_workers(worker_list) mark = Time.now worker_list.each do |w| w[:started_on] = Time.now if not w[:started_on] if mark - w[:started_on] > @death_time + @timeout STDERR.puts "Thread #{w.inspect} is too old, killing." w.raise(StopServer.new("Timed out thread.")) end end 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.
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 |
# File 'lib/mongrel.rb', line 538 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 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.
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 |
# File 'lib/mongrel.rb', line 480 def run BasicSocket.do_not_reverse_lookup=true @acceptor = Thread.new do while true begin client = @socket.accept 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 reap_dead_workers(worker_list) else thread = Thread.new do process_client(client) end thread[:started_on] = Time.now thread.priority=1 @workers.add(thread) sleep @timeout/100 if @timeout > 0 end rescue StopServer STDERR.puts "Server stopped. Exiting." @socket.close if not @socket.closed? break rescue Errno::EMFILE STDERR.puts "Too many open files. Try increasing ulimits." sleep 0.5 end end # now that processing is done we feed enough false onto the request queue to get # each processor to exit and stop processing. # finally we wait until the queue is empty (but only about 10 seconds) @death_time = 10 shutdown_start = Time.now while @workers.list.length > 0 waited_for = (Time.now - shutdown_start).ceil STDERR.print "Shutdown waited #{waited_for} for #{@workers.list.length} requests, could take #{@death_time + @timeout} seconds.\r" if @workers.list.length > 0 sleep 1 reap_dead_workers(@workers.list) end end return @acceptor end |
#stop ⇒ Object
Stops the acceptor thread and then causes the worker threads to finish off the request queue before finally exiting.
565 566 567 568 569 570 571 |
# File 'lib/mongrel.rb', line 565 def stop stopper = Thread.new do exc = StopServer.new @acceptor.raise(exc) end stopper.priority = 10 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.
559 560 561 |
# File 'lib/mongrel.rb', line 559 def unregister(uri) @classifier.unregister(uri) end |