#
# httpserver.rb -- HTTPServer Class
#
# Author: IPR -- Internet Programming with Ruby -- writers
# Copyright (C) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
#
# $IPR: httpserver.rb,v 1.34 2002/02/14 15:10:34 gotoyuzo Exp $

require 'webrick/server'
require 'webrick/config'
require 'webrick/httputils'
require 'webrick/httpstatus'
require 'webrick/httprequest'
require 'webrick/httpresponse'
require 'webrick/httpservlet'

module WEBrick

  class HTTPServerError < ServerError; end

  class HTTPServer < ::WEBrick::GenericServer
    def initialize(config={}, default=Config::HTTP)
      super
      @mount_tab = MountTable.new
      if @config[:DocumentRoot]
        mount("/", HTTPServlet::FileHandler,
              @config[:DocumentRoot], @config[:DirectoryListEnable])
      end
    end

    def run(sock)
      peeraddr = sock.peeraddr[3]
      while true 
        res = HTTPResponse.new(@config)
        req = HTTPRequest.new(@config)
        request_line = nil
        begin
          req.parse(sock)
          service(req, res)
        rescue HTTPStatus::EOFError
          break
        rescue HTTPStatus::RequestTimeout => ex
          access_log(sock, req, res, ex)
          break
        rescue HTTPStatus::Error => ex
          res.set_error(ex)
        rescue HTTPStatus::Status => ex
          res.status = ex.code
        rescue StandardError, NameError => ex # for Ruby 1.6
          @logger.error(ex)
          res.set_error(ex, true)
        end
        if req.request_method == "HEAD"
          res.send_header(sock)
        else
          res.send_response(sock)
        end
        access_log(sock, req, res)
        break unless req.keep_alive?
        break unless res.keep_alive?
      end
    end

    def service(req, res)
      path = req.path
      servlet, options, script_name, path_info = search_servlet(path)
      raise HTTPStatus::NotFound, "`#{path}' not found."  unless servlet
      if servlet.require_path_info? && path_info.empty?
        res.set_redirect(HTTPStatus::MovedPermanently, path + "/")
      end
      req.script_name = script_name
      req.path_info = path_info
      si = servlet.get_instance(@config, *options)
      si.service(req, res)
    end

    def mount(dir, servlet, *options)
      @mount_tab[dir] = [ servlet, options ]
    end

    def mount_proc(dir, proc=nil, &block)
      proc ||= block
      raise HTTPServerError, "must pass a proc or block" unless proc
      mount(dir, HTTPServlet::ProcHandler.new(proc))
    end

    def unmount(dir)
      @mount_tab.delete(dir)
    end
    alias umount unmount

    def search_servlet(path)
      script_name, path_info = @mount_tab.scan(path)
      servlet, options = @mount_tab[script_name]
      if servlet
        [ servlet, options, script_name, path_info ]
      end
    end

    def access_log(sock, req, res, error=nil)
      ad = sock.peeraddr[3]
      rl = req.request_line and rl.sub!(/\x0d?\x0a\z/o, '')
      st = error ? error.code : res.status
      sz = res.body ? res.body.size : 0
      re = req['referer'] || "-"
      ua = req['user-agent'] || "-"

      @logger.info("#{ad} \"#{rl}\" #{st} #{sz} \"#{re}\" \"#{ua}\"")
    end

    class MountTable
      def initialize
        @tab = Hash.new
	compile
      end

      def [](dir)
        dir = normalize(dir)
        @tab[dir]
      end

      def []=(dir, val)
        dir = normalize(dir)
        @tab[dir] = val
        compile
        val
      end

      def delete(dir)
        dir = normalize(dir)
        res = @tab.delete(dir)
        compile
        res
      end

      def scan(path)
        @scanner =~ path
        [ $&, $' ]
      end

      private

      def compile
        k = @tab.keys
        k.sort!
        k.reverse!
        k.collect!{|path| Regexp.escape(path) }
        @scanner = Regexp.new("^(" + k.join("|") +")(?=/|$)")
      end

      def normalize(dir)
        ret = dir ? dir.dup : ""
        ret.sub!(%r|/+$|, "")
        ret
      end
    end

  end
end
