Class: Mongrel::HttpServer

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize(host, port, num_processors = 20, timeout = 120) ⇒ HttpServer

Creates a working server on host:port (strange things happen if port isn’t a Number). Use HttpServer::run to start the server.

The num_processors variable has varying affects on how requests are processed. You’d think adding more processing threads (processors) would make the server faster, but that’s just not true. There’s actually an effect of how Ruby does threads such that the more processors waiting on the request queue, the slower the system is to handle each request. But, the lower the number of processors the fewer concurrent responses the server can make.

20 is the default number of processors and is based on experimentation on a few systems. If you find that you overload Mongrel too much try changing it higher. If you find that responses are way too slow try lowering it (after you’ve tuned your stuff of course).



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/mongrel.rb', line 345

def initialize(host, port, num_processors=20, timeout=120)
  @socket = TCPServer.new(host, port) 

  @classifier = URIClassifier.new
  @req_queue = Queue.new
  @host = host
  @port = port
  @processors = []

  # create the worker threads
  num_processors.times do |i| 
    @processors << Thread.new do
      while client = @req_queue.deq
        Timeout::timeout(timeout) do
          process_client(client)
        end
      end
    end
  end

end

Instance Attribute Details

#acceptorObject (readonly)

Returns the value of attribute acceptor.



329
330
331
# File 'lib/mongrel.rb', line 329

def acceptor
  @acceptor
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.



373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/mongrel.rb', line 373

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, handler = @classifier.resolve(params[Const::REQUEST_URI])

        if handler
          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)
          handler.process(request, response)
        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
    # ignored
  rescue Errno::ECONNRESET
    # ignored
  rescue Errno::EPIPE
    # ignored
  rescue => details
    STDERR.puts "ERROR(#{details.class}): #{details}"
    STDERR.puts details.backtrace.join("\n")
  ensure
    client.close
  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.



456
457
458
# File 'lib/mongrel.rb', line 456

def register(uri, handler)
  @classifier.register(uri, handler)
end

#runObject

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.



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
# File 'lib/mongrel.rb', line 418

def run
  BasicSocket.do_not_reverse_lookup=true
  @acceptor = Thread.new do
    Thread.current[:stopped] = false

    while not Thread.current[:stopped]
      begin
        @req_queue << @socket.accept
      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.
    @processors.length.times { @req_queue << false }

    # finally we wait until the queue is empty
    while @req_queue.length > 0
      STDERR.puts "Shutdown waiting for #{@req_queue.length} requests" if @req_queue.length > 0
      sleep 1
    end
  end

  @acceptor.priority = 1

  return @acceptor
end

#stopObject

Stops the acceptor thread and then causes the worker threads to finish off the request queue before finally exiting.



468
469
470
471
472
473
474
475
# File 'lib/mongrel.rb', line 468

def stop
  stopper = Thread.new do 
    @acceptor[:stopped] = true
    exc = StopServer.new
    @acceptor.raise(exc)
  end
  stopper.priority = 10
end

#unregister(uri) ⇒ Object

Removes any handler registered at the given URI. See Mongrel::URIClassifier#unregister for more information.



462
463
464
# File 'lib/mongrel.rb', line 462

def unregister(uri)
  @classifier.unregister(uri)
end