require 'erb'
require 'cgi'
require 'webrick'
include WEBrick
require 'view_helper'
class FavIconHandler < HTTPServlet::AbstractServlet
LOCAL_PATH = File.join(File.dirname(__FILE__), '../favicon.png')
ICO_FILE = File.open(LOCAL_PATH, "rb") { |f| f.read }
def do_GET(req, res)
res['content-type'] = 'image/png'
res.body = ICO_FILE
end
end
class ActionControllerServlet < HTTPServlet::AbstractServlet
@@template_root = "./views"
def self.template_root() @@template_root end
def self.template_root=(template_root) @@template_root = template_root end
include ViewHelper
def do_POST(req, res) do_GET(req, res) end
def do_GET(req, res)
@req, @res = req, res
@res['Content-Type'] = "text/html; charset=utf-8"
@res['Pragma'] = "no-cache"
@res['Cache-Control'] = "no-cache"
@params = @req.query
@assigns = {}
@performed_render = @performed_redirect = false
@logger.info "Performing #{action_name} (#{request_path.join('/')})"
@logger.info " Parameters: #{@params.inspect}"
@logger.info " Cookies: #{@req.cookies.collect { |c| "#{c.name} => #{c.value}" }.join(", ") }"
perform_action
@res
end
protected
def template_root() self.class.template_root end
# Issues a HTTP 302 (location) redirect to the specified path. Query string
# parameters can be specified in the params hash and are automatically URL escaped.
# An anchor can also be specified (as a string).
def redirect_path(path, params = nil, anchor = nil)
path << build_query_string(params) unless params.nil?
path << "\##{anchor}" unless anchor.nil?
@res.set_redirect WEBrick::HTTPStatus::MovedPermanently, path
@performed_redirect = true
end
# Redirects the browser to another action within the current controller, so redirect_path "pages"
# within a controller called WikiController would take the user to "http://www.example.com/wiki/pages"
def redirect_action(action, params = nil, anchor = nil)
redirect_path "/#{controller_name}/#{action}", params, anchor
end
# Compiles the template response and adds it to the response. If no template_name has been
# supplied, the action parameter parsed to the controller is used. So invoking
# render on a object initialized with myController.new("show_person") will compile the template
# located at "../views/show_person.rhtml". Notice that the ".rhtml" extension is postfixed
# to the template_name regardless of one was passed or that the action parameter was used.
def render(template_name = "#{controller_name}/#{action_name}")
@performed_render = true
@logger.info "Rendering: #{template_name}"
add_instance_variables_to_assigns
@res.body = template_result "#{template_root}/#{template_name}.rhtml"
end
def render_text(text)
@performed_render = true
@logger.info "Rendering in text"
@res.body = text
end
# Wrapper around render that presumes the current controller is used as a base for the action,
# so render_action("page") in WikiController will be equal to render("wiki/page")
def render_action(action_name)
render "#{controller_name}/#{action_name}"
end
def sub_template(template_name)
template_result "#{template_root}/#{template_name}.rhtml"
end
# Returns the value of the cookie matching +name+ (or nil if none is found).
def read_cookie(key)
cookies = @req.cookies.select { |cookie| cookie.name == key }
cookies.empty? ? nil : cookies.first.value
end
def write_cookie(key, value, permanent = false)
@logger.info "Writing cookie: #{key} => #{value}"
cookie = WEBrick::Cookie.new(key, value)
cookie.path = "/"
cookie.expires = Time.local(2030) if permanent
@res.cookies << cookie
end
# Returns the last part of the uri path ("page" in "/wiki/page") by default, but
# can be overwritten to implement mod_rewrite-like behaviour, such as "page" in "/wiki/page/home"
def action_name
@req.path.to_s.split(/\//).last
end
# Returns an array with each of the parts in the request as an element. So /something/cool/dude
# returns ["something", "cool", "dude"]
def request_path
request_path_parts = @req.path.to_s.split(/\//)
request_path_parts.length > 1 ? request_path_parts[1..-1] : []
end
# Can be overwritten by a controller class to implement shared behaviour for all the actions in
# the class, such as security measures
def before_action() end
private
# Wraps around all action calls to ensure the session is properly updated and closed.
# Figures out whether an action, such as "list", is implemented as show_list or do_list.
# The show_* type has precedence and automatically calls render after execution.
def perform_action
if before_action == false then return end
if action_methods.include?(action_name)
send(action_name)
render unless @performed_render || @performed_redirect || !@res.body.empty?
elsif template_exists_for_action
render
else
raise RuntimeError, "No action responded to #{action_name}", caller
end
rescue Exception => details
raise if ['WEBrick', 'Net'].include? details.class.name.split('::')[0]
@error_details = details
render 'error'
end
def add_instance_variables_to_assigns
instance_variables.each { |var|
next if protected_instance_variables.include?(var)
@assigns[var[1..-1]] = instance_variable_get(var)
}
end
def protected_instance_variables
[ "@assigns", "@performed_redirect", "@performed_render" ]
end
def action_methods
methods - Object.instance_methods
end
# Returns true if a template exists in the controller directory for the specified action_name,
# which would mean true if called for WikiController#changes and "/views/wiki/changes.rhtml" existed.
def template_exists_for_action
File.exist? action_template_path
end
def action_template_path(action = action_name)
"#{template_root}/#{controller_name}/#{action}.rhtml"
end
def template_result(template_path)
@assigns.each { |key, value| instance_variable_set "@#{key}", value }
begin
ERB.new(IO.readlines(template_path).join).result(binding)
rescue Exception => detail
@logger.error "Error processing #{template_path}"
line = /:(\d+):/.match(detail.backtrace[0]).captures[0].to_i - 172
@logger.error "On Line: #{line}"
@logger.error detail
src = ERB.new(IO.readlines(template_path).join).src
lines = src.split("\n")[(line-1)..(line+1)]
@logger.error "\n\n" + lines.join("\n\n") + "\n\n"
raise
end
end
# Converts the class name from something like "OneModule::TwoModule::NeatController"
# to "neat".
def controller_name
self.class.to_s.split("::").last.sub(/Controller/, "").downcase
end
# Returns a query string with escaped keys and values from the passed hash.
def build_query_string(hash)
elements = []
hash.each { |key, value| elements << "#{CGI.escape(key)}=#{CGI.escape(value.to_s)}" }
"?" + elements.join("&")
end
end