|
|
%w(rubygems rack merb/core_ext merb/router json uri logger).each{|dep|require dep}
module Halcyon
def self.version
[0,5,0].join('.')
end
class Application
def initialize(options={})
@options = options
@logger = @options[:logger]
@logger.progname = self.class.to_s
@logger.formatter = proc{|s,t,p,m|"#{s} [#{t.strftime("%Y-%m-%d %H:%M:%S")}] (#{$$}) #{p} :: #{m}\n"}
@logger.info "Starting up..."
startup if respond_to? :startup
@logger.info "Started. Listening on #{@options[:port]}."
finalize = Proc.new do
@logger.info "Shutting down #{$$}."
exit
end
%w(INT KILL TERM QUIT HUP).each{|sig|trap(sig, finalize)} # http://en.wikipedia.org/wiki/Signal_%28computing%29
end
def call(env)
@time_started = Time.now
@env = env
@request = Rack::Request.new(@env)
@response = Rack::Response.new
@response['Content-Type'] = "text/plain" # "application/json"
@response['User-Agent'] = "JSON/#{JSON::VERSION} Compatible (en-US) Halcyon::Application/#{Halcyon.version}"
@env['halcyon.route'] = Router.route(@env)
response = _dispatch(@env['halcyon.route'])
@response.status = response[:status]
@response.write response.to_json
@time_finished = Time.now - @time_started
req_time, req_per_sec = ((@time_finished*1e4).round.to_f/1e4), (((1.0/@time_finished)*1e2).round.to_f/1e2)
@logger.info "[#{@response.status}] #{@env['REQUEST_URI']} (#{req_time}s;#{req_per_sec}req/s)"
@response.finish
end
def _dispatch(route)
@params = route.reject{|key, val| [:action, :module].include? key}
@params.merge!(query_params)
@logger << "\nParams: #{@params.inspect}\n"
# make sure that the right module/action is called based on the route
case route[:module]
when NilClass
# not a part of a module
send(route[:action].to_sym)
when Symbol
# module name specified
self.class.class_eval { include const_get(route[:module]) }
self.class.const_get(route[:module]).instance_method(route[:action].to_sym).bind(self).call
# TODO: figure out how to do this... the bound object has to be kind_of? the module where it was plucked from
when String
# pulled from URL, so camelize (from merb/core_ext) and symbolize first
_dispatch(route.merge(:module => route[:module].camelize.to_sym))
end
end
def self.route
if block_given?
Router.prepare do |router|
Router.default_to yield(router) || {:action => 'not_found'}
end
end
end
class Router < Merb::Router
def self.default_to route
@@default_route = route.is_a?(Hash) ? route : {:action => 'not_found'}
end
def self.route(env)
uri = URI.parse(env['REQUEST_URI'] || env['PATH_INFO']).path
path = (uri ? uri.split('?').first : '').sub(/\/+/, '/')
path = path[0..-2] if (path[-1] == ?/) && path.size > 1
req = Struct.new(:path, :method).new(path, env['REQUEST_METHOD'].downcase.to_sym)
route = self.match(req, {})
if route[0].nil?
env['halcyon.logger'].debug "No route found. Using default." if env['halcyon.logger'].is_a? Logger
@@default_route
else
route[1]
end
end
end
def params
@params
end
def query_params
@env['QUERY_STRING'].split(/&/).inject({}){|h,kp| k,v = kp.split(/=/); h[k] = v; h}.symbolize_keys!
end
def uri
# special parsing is done to remove the protocol, host, and port that
# some Handlers leave in there. (Fixes inconsistencies.)
URI.parse(@env['REQUEST_URI'] || @env['PATH_INFO']).path
end
def method
@env['REQUEST_METHOD'].downcase.to_sym
end
def post
@req.POST.symbolize_keys!
end
def get
@req.GET.symbolize_keys!
end
def ok(msg='OK')
{:status => 200, :body => msg}
end
def not_found(msg='Not Found')
{:status => 400, :body => msg}
end
end
class Logger < ::Logger; end #:nodoc:
class Client; end #:nodoc:
end
|