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). Future versions of Mongrel will make this more dynamic (hopefully).



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/mongrel.rb', line 317

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
  @num_processors = num_processors
  @timeout = timeout

  @num_processors.times {|i| Thread.new do
      while client = @req_queue.deq
        begin
          Timeout.timeout(@timeout) do
            process_client(client)
          end
        rescue Timeout::Error
          STDERR.puts "WARNING: Request took longer than #@timeout second timeout"
        end
      end
    end
  }
end

Instance Attribute Details

#acceptorObject (readonly)

Returns the value of attribute acceptor.



300
301
302
# File 'lib/mongrel.rb', line 300

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.



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/mongrel.rb', line 347

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
          params[Const::GATEWAY_INTERFACE]=Const::GATEWAY_INTERFACE_VALUE
          params[Const::REMOTE_ADDR]=client.peeraddr[3]
          params[Const::SERVER_NAME]=@host
          params[Const::SERVER_PORT]=@port
          params[Const::SERVER_PROTOCOL]=Const::SERVER_PROTOCOL_VALUE
          params[Const::SERVER_SOFTWARE]=Const::MONGREL_VERSION

          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
      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.



412
413
414
# File 'lib/mongrel.rb', line 412

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.



399
400
401
402
403
404
405
406
# File 'lib/mongrel.rb', line 399

def run
  BasicSocket.do_not_reverse_lookup=true
  @acceptor = Thread.new do
    while true
      @req_queue << @socket.accept
    end
  end
end

#unregister(uri) ⇒ Object

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



418
419
420
# File 'lib/mongrel.rb', line 418

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