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.
-
#workers ⇒ Object
readonly
Returns the value of attribute workers.
Class Method Summary collapse
-
.httpdate(date) ⇒ Object
Given the a time object it converts it to GMT and applies the RFC1123 format to it.
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) ⇒ 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.
380 381 382 383 384 385 386 387 388 |
# File 'lib/mongrel.rb', line 380 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 end |
Instance Attribute Details
#acceptor ⇒ Object (readonly)
Returns the value of attribute acceptor.
363 364 365 |
# File 'lib/mongrel.rb', line 363 def acceptor @acceptor end |
#classifier ⇒ Object (readonly)
Returns the value of attribute classifier.
365 366 367 |
# File 'lib/mongrel.rb', line 365 def classifier @classifier end |
#workers ⇒ Object (readonly)
Returns the value of attribute workers.
364 365 366 |
# File 'lib/mongrel.rb', line 364 def workers @workers end |
Class Method Details
.httpdate(date) ⇒ Object
Given the a time object it converts it to GMT and applies the RFC1123 format to it.
545 546 547 |
# File 'lib/mongrel.rb', line 545 def HttpServer.httpdate(date) date.gmtime.strftime(Const::RFC_1123_DATE_FORMAT) 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.
396 397 398 399 400 401 402 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 |
# File 'lib/mongrel.rb', line 396 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 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 parser.reset data << client.readpartial(Const::CHUNK_SIZE) end end rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL # ignored rescue => details STDERR.puts "ERROR(#{details.class}): #{details}" 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.
450 451 452 453 454 455 456 457 458 |
# File 'lib/mongrel.rb', line 450 def reap_dead_workers(worker_list) mark = Time.now worker_list.each do |w| if mark - w[:started_on] > 10 * @timeout STDERR.puts "Thread #{w.inspect} is too old, killing." w.raise(StopServer.new("Timed out thread.")) end end end |
#register(uri, handler) ⇒ 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.
513 514 515 516 517 518 519 520 521 522 523 524 525 |
# File 'lib/mongrel.rb', line 513 def register(uri, handler) 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) handlers << handler 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.
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 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 |
# File 'lib/mongrel.rb', line 463 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 while @workers.list.length > 0 STDERR.puts "Shutdown waiting for #{@workers.list.length} requests" if @workers.list.length > 0 sleep 1 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.
536 537 538 539 540 541 542 |
# File 'lib/mongrel.rb', line 536 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.
530 531 532 |
# File 'lib/mongrel.rb', line 530 def unregister(uri) @classifier.unregister(uri) end |