class Egalite::Handler

Attributes

routes[RW]

Public Class Methods

new(opts = {}) click to toggle source
# File lib/egalite.rb, line 406
def initialize(opts = {})
  @routes = opts[:routes] || Route.default_routes
  
  db = opts[:db]
  @db = db
  @opts = opts
  @env = Environment.new(db, opts)
  opts[:static_root] ||= "static/"

  @template_path = opts[:template_path] || 'pages/'
  @template_path << '/' if @template_path[-1..-1] != '/'
  @template_engine = opts[:template_engine] || HTMLTemplate
  
  @profile_logger = opts[:profile_logger]
  @notfound_template = opts[:notfound_template]
  if opts[:error_template_file]
    @error_template = File.open(opts[:error_template_file]).read
  else
    @error_template = opts[:error_template]
  end
  @admin_emails = opts[:admin_emails]
  @email_from = opts[:email_from]
  @exception_log_table = opts[:exception_log_table]
  if @exception_log_table
    Egalite::ErrorLogger.table = db[@exception_log_table]
    Egalite::ErrorLogger.admin_emails = @admin_emails
    Egalite::ErrorLogger.email_from = @email_from
  end
end

Public Instance Methods

call(rack_env) click to toggle source
# File lib/egalite.rb, line 702
def call(rack_env)
  # set up logging
  
  res = nil

  req = Rack::Request.new(rack_env)
  
  begin
    ereq = Request.new(
      :rack_request => req,
      :rack_env => rack_env,
      :handler => self
    )

    # parameter handling
    params = stringify_hash(req.params)

    puts "before-cookie: #{req.cookies.inspect}" if @opts[:cookie_debug]
    
    ereq.params = params
    ereq.cookies = req.cookies

    authorization_keys = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION']
    key = authorization_keys.detect { |key| rack_env.has_key?(key) }
    ereq.authorization = rack_env[key] if key
    
    if @opts[:session_handler]
      ereq.session = @opts[:session_handler].new(@env,ereq.cookies, @opts[:session_opts] || {})
      ereq.session.load
    end
    
    res = dispatch(req.path_info, params, req.request_method, ereq, true)
    res = res.to_a

    puts "after-cookie: #{res[1]['Set-Cookie'].inspect}" if @opts[:cookie_debug]
    
    if res[0] == 200
      if res[1]['Content-Type'] !~ /charset/i and res[1]['Content-Type'] =~ /text\/html/i
        res[1]["Content-Type"] = @opts[:charset] || 'text/html; charset=utf-8'
      end
    end
    
   rescue Exception => e
    raise e if $raise_exception
    
    begin
      # write error log
      logid = nil
      if @exception_log_table and not e.is_a?(UserError)
        logid = ErrorLogger.write_exception(e,{:ipaddress => req.ip, :url => req.url})
      end
      
      # show exception
      
      if @error_template
        values = {}
        values[:logid] = logid if logid
        values[:exception] = e.to_s
        values[:backtrace] = e.backtrace
        values[:message] = e.message if e.is_a?(UserError) or e.is_a?(SystemError)
        values[:usererror] = true if e.is_a?(UserError)
        html = @template_engine.new.handleTemplate(@error_template.dup,values)
        res = [500, {"Content-type"=>"text/html; charset=utf-8"}, [html]]
      else
        res = display_internal_server_error(e)
      end
    rescue Exception => e2
      res = display_internal_server_error(e2)
    end
  end
  
  res = res.to_a
  p res if @opts[:response_debug]
  res
end
dispatch(path, params, method, req, first_call = false) click to toggle source
# File lib/egalite.rb, line 645
def dispatch(path, params, method, req, first_call = false)
  # routing
  (controller_name, action_name, path_params, prmhash) = nil
  (controller, action) = nil
  
  route = @routes.find { |route|
    puts "Routing: matching: #{route.inspect}" if RouteDebug
    route_result = route.match(path)
    (controller_name, action_name, path_params, prmhash) = route_result
    next if route_result == nil
    puts "Routing: pre-matched: #{route_result.inspect}" if RouteDebug
    (controller, action) = get_controller(controller_name, action_name, method)
    true if controller
  }
  return display_notfound unless controller

  puts "Routing: matched: #{controller.class} #{action}" if RouteDebug
  params = prmhash.merge(params)
  
  req.route = route
  req.controller = controller_name
  req.controller_class = controller
  req.action = action_name
  req.action_method = action
  req.inner_path = path
  req.path_params = path_params
  req.path_info = path_params.join('/')

  res = run_controller(controller, action, req)
  
  if first_call
    controller.after_filter(res.to_a)
    
    # access log
    t = Time.now - req.time
    log = [req.time.iso8601, req.ipaddr, t, req.url, req.referrer]
    log += controller.log_values.to_a
    line = log.map {|s| s.to_s.gsub(/\t/,'')}.join("\t").gsub(/\n/,'')
    AccessLogger.write(line)
  end
  res
end
inner_dispatch(req, values) click to toggle source
# File lib/egalite.rb, line 544
def inner_dispatch(req, values)
  # recursive controller call to handle include tag or delegate.
  stringified = StringifyHash.create(values)
  (path, params) = req.route.get_path_and_params_from_params(stringified)
  newreq = req.clone
  newreq.params = params
  method = 'GET'
  method = values[:http_method] if values[:http_method]
  dispatch(path, params, method, newreq)
end
run_controller(controller, action, req) click to toggle source
# File lib/egalite.rb, line 554
def run_controller(controller, action, req)
  # invoke controller
  controller.env = @env
  controller.req = req
  controller.params = req.params
  
  before_filter_result = controller.before_filter
  if before_filter_result != true
    return before_filter_result if before_filter_result.is_a?(Array)
    return [200,{'Content-Type' => "text/html"},[before_filter_result]] if before_filter_result.is_a?(String)
    return forbidden unless before_filter_result.respond_to?(:command)
    response = case before_filter_result.command
     when :delegate
      inner_dispatch(req, before_filter_result.param)
     when :redirect
      redirect(before_filter_result.param)
     when :notfound
      display_notfound
     else
      forbidden
    end
    set_cookies_to_response(response,req)
    return response
  end
  
  nargs = controller.method(action).arity
  args = req.path_params[0,nargs.abs] || []
  if nargs > 0
    args.size.upto(nargs-1) { args << nil }
  end
  raise SecurityError unless controller.respond_to?(action, false)

  s = Time.now
  values = controller.send(action,*args)
  t = Time.now - s
  @profile_logger.puts "#{Time.now}: ctrl #{t}sec #{controller.class.name}.#{action} (#{req.path_info})" if @profile_logger
  
  values = controller.after_filter_return_value(values)
  
  # result handling
  result = if values.respond_to?(:command)
    case values.command
     when :delegate
      inner_dispatch(req, values.param)
     when :redirect
      redirect(values.param)
     when :notfound
      display_notfound
    end
  elsif values.is_a?(Array)
    values
  elsif values.is_a?(String)
    html = controller.after_filter_html(values)
    [200,{'Content-Type' => "text/html"},[html]]
  elsif values.is_a?(Rack::Response)
    values.to_a
  elsif values == nil
    raise "egalite error: controller returned nil as a response."
  else
    htmlfile = controller.template_file
    unless htmlfile
      htmlfile = [req.controller,req.action].compact.join('_')
      htmlfile = 'index' if htmlfile.blank?
      htmlfile += '.html'
    end
    html = load_template(@template_path + htmlfile)
    return [404, {"Content-Type" => "text/plain"}, ["Template not found: #{htmlfile}\n"]] unless html
    
    # apply on_html_load filter
    html = controller.filter_on_html_load(html, htmlfile)
    
    # apply html template
    template = @template_engine.new
    template.controller = controller

    s = Time.now
    template.handleTemplate(html,values) { |values|
      inner_dispatch(req,values)[2].join("")
    }
    t = Time.now - s
    
    html = controller.after_filter_html(html)
    
    @profile_logger.puts "#{Time.now}: view #{t}sec #{controller.class.name}.#{action} (#{req.path_info})" if @profile_logger

    [200,{"Content-Type"=>"text/html"},[html]]
  end
  set_cookies_to_response(result,req)
  return result
end

Private Instance Methods

display_internal_server_error(e) click to toggle source
# File lib/egalite.rb, line 503
def display_internal_server_error(e)
  html = [
  "<html><body>",
  "<h1>Internal server error.</h1>"
  ]
  if ShowException
    html += [
      "<p>Exception: #{escape_html(e.to_s)}</p>",
      "<h2>Back trace</h2>",
      "<p>#{e.backtrace.map{|s|escape_html(s)}.join("<br/>\n")}</p>"
    ]
  end
  html << "</body></html>"
  [500, {'Content-Type' => 'text/html'}, [html.join("\n")]]
end
display_notfound() click to toggle source
# File lib/egalite.rb, line 450
def display_notfound
  if @notfound_template
    [404, {"Content-Type" => "text/html"}, [load_template(@notfound_template)]]
  else
    [404, {"Content-Type" => "text/plain"}, ['404 not found']]
  end
end
escape_html(s) click to toggle source
# File lib/egalite.rb, line 446
def escape_html(s)
  Rack::Utils.escape_html(s)
end
forbidden() click to toggle source
# File lib/egalite.rb, line 499
def forbidden
  [403, {'Content-Type' => 'text/plain'}, ['Forbidden']]
end
get_controller(controllername,action, method) click to toggle source
# File lib/egalite.rb, line 461
def get_controller(controllername,action, method)
  # HTTPメソッドと一致するアクション名は却下する(メソッド名で受け付けるべき)
    # /controller/get みたいな名前でgetがパスパラメータとして渡るべき
    # といいつつ後方互換性を考慮してとりあえずget/post/headだけ却下
  return nil if %w[get post head].include?(action&.downcase)
  
  action = method if action.blank?
  action.downcase!
  action.gsub!(/[^0-9a-z_]/,'')
  
  return nil if action == ""
  Controller.new.methods.each { |s| return nil if action.downcase == s.to_s }
  
  controllername ||= ''
  controllername = controllername.split('/').map { |c|
    c.downcase!
    # 英数字以外が含まれるものはコントローラーとはみなさない。
    return nil if c =~ /[^0-9a-z]/
    c.capitalize
  }.join
  controllername = 'Default' if controllername.blank?
  
  kontroller = Object.const_get(controllername+'Controller') rescue nil
  return nil unless kontroller
  
  controller = kontroller.new
  method = method.downcase
  
  unless controller.respond_to?(action)
    if controller.respond_to?("#{action}_#{method}")
      action = "#{action}_#{method}"
    else
      return nil
    end
  end
  [controller, action]
end
load_template(tmpl) click to toggle source
# File lib/egalite.rb, line 437
def load_template(tmpl)
  # to expand: template caching
  if File.file?(tmpl) && File.readable?(tmpl)
    open(tmpl) { |f| f.read }
  else
    nil
  end
end
redirect(url) click to toggle source
# File lib/egalite.rb, line 457
def redirect(url)
  url = url.gsub(/\r/, "%0D").gsub(/\n/, "%0A")
  [302,{'Location' => url}, [url]]
end
set_cookies_to_response(response,req) click to toggle source
# File lib/egalite.rb, line 519
def set_cookies_to_response(response,req)
  req.cookies.each { |k,v|
    cookie_opts = @opts[:cookie_opts] || {}
    unless v.is_a?(Hash)
      req.cookies[k] = {
        :value => v.to_s,
        :expires => Time.now + (cookie_opts[:expire_after] || 3600),
        :path => cookie_opts[:path] || '/',
        :secure => cookie_opts[:secure] || false
      }
    end
  }
  a = req.cookies.map { |k,v|
    s = "#{Rack::Utils.escape(k)}=#{Rack::Utils.escape(v[:value])}"
    s += "; domain=#{v[:domain]}" if v[:domain]
    s += "; path=#{v[:path]}" if v[:path]
    s += "; expires=#{v[:expires].clone.gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT")}" if v[:expires]
    s += "; secure" if v[:secure]
    s
  }
  s = a.join("\n")
  response[1]['Set-Cookie'] = s
end
stringify_hash(params) click to toggle source
# File lib/egalite.rb, line 689
def stringify_hash(params)
  sh = StringifyHash.new
  params.each { |k,v|
    if v.is_a?(Hash)
      sh[k] = stringify_hash(v)
    else
      sh[k] = v
    end
  }
  sh
end