[Nitro] [PATCH?] Mongrel adapter for Nitro

Joshua Hoke nitro at tap.homeip.net
Sat Jan 28 12:21:12 EST 2006


On Fri, Jan 27, 2006 at 01:23:18PM +0200, George Moschovitis wrote:
> In case anyone has some free time this weekend here is a nice little project:
> 
> write a Mongrel (Webrick alternative) adapter for Nitro.

Here is my rather simple attempt. It handles at least /index.xhtml
and /test.txt (in the public/ directory), I didn't test it with anything
more complex. I'm sure it would need more work to support a
sophisticated application but I don't really have any of those lying
around to test. :)

I based it heavily off the webrick adapter under Nitro 0.26.0.

Joshua Hoke
-------------- next part --------------
require 'mongrel'
require 'stringio'

require 'glue/flexob'
require 'nitro/cgi'
require 'nitro/context'
require 'nitro/dispatcher'

# Speeds things up, more comaptible with OSX.

Socket.do_not_reverse_lookup = true

module Mongrel
    class HttpRequest
        def method_missing(name, *args)
            if @params.has_key?(name.to_s.upcase)
                return @params[name.to_s.upcase]
            elsif name.to_s =~ /\A(.*)=\Z/ && @params.has_key?($1.upcase)
                @params[$1.upcase] = args[0]
            else
                    super
            end
        end
    end
end
                
   
module Nitro

class Mongrel

  class << self
    attr_accessor :mongrel

    # Start the Webrick adapter.
    
    def start(server)
      # TODO add logging.

     mongrel_options = server.options.dup

     mongrel_options.update(
        :BindAddress => server.address, 
        :Port => server.port, 
        :DocumentRoot => server.public_root
      )
      @mongrel = ::Mongrel::HttpServer.new(mongrel_options[:BindAddress],
        mongrel_options[:Port])
      
      trap('INT') { stop } # will this work?

      @mongrel.register('/', MongrelAdapter.new(server))
      
      initialize_mongrel(server)
      
      @mongrel_thread = @mongrel.run
      @mongrel_thread.join
    end
    
    # Stop the Mongrel adapter.
    
    def stop
        @mongrel_thread.kill
    end
    
    # Override this method to perform customized mongrel
    # initialization.
    
    def initialize_mongrel(server)
    end
    
  end

end

# A special handler for Xhtml files.

#class XhtmlFileHandler < WEBrick::HTTPServlet::DefaultFileHandler
#  def do_GET(req, res)
#    res['content-type'] = 'text/html'
#    res.body = '<html><body>Permission denied</body></html>'
#  end
#end

# A Mongrel Adapter for Nitro.
class MongrelAdapter < ::Mongrel::HttpHandler
    attr_accessor :server

    STATUS_CODES = {200 => "OK", "304" => "Not Modified",
            "404" => "Not found", "500" => "Server Error"}

    def initialize(server)
        @server = server
    end

    def process(req, res)
        handle(req, res)
    end

  # Handle a static file. Also handles cached pages.

  def handle_file(req, res)
    begin
      rewrite(req)
      # TODO handle If-Modified-Since and add Last-Modified headers
      filename = [@server.public_root, req.path_info].join("/")
      File.open(filename, "r") { |f|
        # TODO look up mime type
        # TODO check whether path circumvents public_root directory?
        size = File.size(filename)
        res.socket << "HTTP/1.1 200 OK\r\n"
        res.socket << "Content-type: text/plain\r\n"
        res.socket << "Content-length: #{size}\r\n"
        res.socket << "\r\n"
        res.socket << f.read # XXX inefficient for large files, may cause leaks
      }
      return true
    rescue Object => ex
      return false
    ensure
      unrewrite(req)
    end
  end

  # Handle the request.

  def handle(req, res)
    unless handle_file(req, res)
      path = req.script_name

      unless path =~ /\./
        begin
          context = Context.new(@server)

          context.in = StringIO.new(req.body || "")

          context.headers = {}
          req.params.each { |h, v|
            if h =~ /\AHTTP_(.*)\Z/
                context.headers[$1.gsub("_", "-")] = v
            end
            context.headers[h] = v
          }
          # context.headers.update(req.meta_vars)

          context.headers['REQUEST_URI'] = context.headers['SCRIPT_NAME']
          context.headers['REQUEST_URI'] = '/' unless
            context.headers['REQUEST_URI'].length > 0

          # gmosx: make compatible with fastcgi.
          #context.headers['REQUEST_URI'].slice!(/http:\/\/(.*?)\//)
          # context.headers['REQUEST_URI'] << '/'
          context.headers['REQUEST_URI'] = '/' + context.headers['REQUEST_URI']

          Cgi.parse_params(context)
          Cgi.parse_cookies(context)

          context.render(path)
          
          res.socket << "HTTP/1.1 #{context.status.to_s} "
          if STATUS_CODES.has_key? context.status
            res.socket << STATUS_CODES[context.status]
          else
            res.socket << "Unknown Status Code"
          end
          res.socket << "\r\n"

          headers = context.response_headers
          headers["Content-length"] = context.out.size
          headers.each { |h,v| res.socket << "#{h}: #{v}\r\n" }
          res.socket << "\r\n"

          # TODO handle setting cookies
          res.socket << context.out
          
          context.close
        ensure
         Og.manager.put_store if defined?(Og) and Og.respond_to?(:manager)
        end
      end
    end
  end

  def rewrite(req)
    if req.path_info == '/' || req.path_info == ''
        req.path_info = '/index.html'
    elsif req.path_info =~ /^([^.]+)$/
        req.path_info = '#{$1}/index.html'
    end
  end

  # Rewrite back to the original path.

  def unrewrite(req)
    if req.path_info == '/index.html'
        req.path_info = '/'
    elsif req.path_info =~ /^([^.]+)\/index.html$/    
        req.path_info = $1
    end
  end
end

end

# * George Moschovitis <gm at navel.gr>
# * Guillaume Pierronnet <guillaume.pierronnet at laposte.net>
# * Bryan Soto <bryan.a.soto at gmail.com>
# * Joshua Hoke
-------------- next part --------------
--- /usr/local/lib/ruby/gems/1.8/gems/nitro-0.26.0/lib/nitro/server/runner.rb	2006-01-28 10:52:40.000000000 -0500
+++ nitro/server/runner.rb	2006-01-28 11:31:59.000000000 -0500
@@ -144,6 +144,11 @@
         @server = :cgi
         Logger.set(Logger.new('log/app.log'))
       end
+
+      opts.on('-m', '--mongrel', 'Use the Mongrel Ruby server.') do
+        @server = :mongrel
+        # XXX handle logging
+      end
       
       opts.on('-C', '--console', 'Start a console attached to an instance of the application.') do
         if RUBY_PLATFORM =~ /mswin32/
@@ -296,6 +301,13 @@
           puts "==> Press Ctrl-C to shutdown; Run with --help for options.\n\n"    
           Webrick.start(server)
 
+        when :mongrel
+          require 'nitro/adapter/mongrel'
+          puts "==> Listening at #{server.address}:#{server.port}."
+          puts "==> Press Ctrl-C to shutdown; Run with --help for options.\n\n"
+
+          Mongrel.start(server)
+
         when :lhttpd
           require 'nitro/adapter/fastcgi'
           puts "==> Launching lighttpd (FastCGI)."


More information about the Nitro-general mailing list