lib/sanford/host.rb in sanford-0.1.0 vs lib/sanford/host.rb in sanford-0.2.0

- old
+ new

@@ -1,138 +1,95 @@ -# Sanford's Host mixin is used to define service hosts. When mixed into a class -# it provides the interface for configuring the service host and for adding -# versioned services. It also contains the logic for routing a request to a -# a service handler. -# -# Options: -# * `name` - A string for naming this host. This can be used when specifying -# a host with the rake tasks and will be used to name the PID -# file. Defaults to the class's name. -# * `ip` - The string for the ip that the TCP Server should bind to. This -# defaults to '0.0.0.0'. -# * `port` - The integer for the port that the TCP Server should bind to. -# This isn't defaulted and must be provided. -# * `pid_dir` - The directory to write the PID file to. This is defaulted to -# Dir.pwd. -# * `logger` - The logger to use if the Sanford server logs messages. This is -# defaulted to an instance of Ruby's Logger. -# -require 'logger' require 'ns-options' require 'pathname' -require 'sanford/config' -require 'sanford/exception_handler' require 'sanford/exceptions' -require 'sanford/service_handler' +require 'sanford/logger' module Sanford + module Host - # Notes: - # * When Host is included on a class, it needs to mixin NsOptions and define - # the options directly on the class (instead of on the Host module). - # Otherwise, NsOptions will not work correctly for the class. - def self.included(host_class) - host_class.class_eval do - include NsOptions - extend Sanford::Host::Interface + class Configuration + include NsOptions::Proxy - options :config do - option :name, String, :default => host_class.to_s - option :ip, String, :default => '0.0.0.0' - option :port, Integer - option :pid_dir, Pathname, :default => Dir.pwd - option :logger, :default => proc{ Sanford::NullLogger.new } + # A Host's configuration is a seperate ns-options proxy class because + # `Host` is a module, so it itself cannot be a ns-options proxy (and + # still function as a mixin). Also, since it is making the `Host` + # a `Singleton`, mixing that with `NsOptions::Proxy` could have strange + # effects (messing up someone's `initialize`). Thus, the `Configuration` + # is a separate class and not on the `Host` directly. - option :exception_handler, :default => Sanford::ExceptionHandler - option :versioned_services, Hash, :default => {} - end + option :name, String + option :ip, String, :default => '0.0.0.0' + option :port, Integer + option :pid_dir, Pathname, :default => Dir.pwd + option :logger, :default => proc{ Sanford::NullLogger.new } + option :verbose_logging, :default => true + option :error_proc, Proc, :default => proc{ } + + def initialize(host) + self.name = host.class.to_s end - Sanford.config.hosts.add(host_class) + end - INTERFACE_OPTIONS = [ :name, :ip, :port, :pid_dir, :logger, :exception_handler ] - - # Notes: - # * The `initialize` takes the values configured on the class and merges - # the passed in options. This is used to set the individual instance's - # configuration (which allows overwriting options like the port). - def initialize(options = nil) - options = self.remove_nil_values(options) - config_options = self.class.config.to_hash.merge(options) - self.config.apply(config_options) - raise(Sanford::InvalidHostError.new(self.class)) if !self.port + def self.included(host_class) + host_class.class_eval do + include Singleton + extend Sanford::Host::ClassMethods + end + Sanford.register(host_class) end - INTERFACE_OPTIONS.each do |name| + attr_reader :configuration, :versioned_services - define_method(name) do - self.config.send(name) - end + def initialize + @configuration = Configuration.new(self) + @versioned_services = {} + end + def name(*args) + self.configuration.name *args end - def run(request) - request_handler(request).run + def ip(*args) + self.configuration.ip *args end - def inspect - reference = '0x0%x' % (self.object_id << 1) - "#<#{self.class}:#{reference} ip=#{self.config.ip.inspect} " \ - "port=#{self.config.port.inspect}>" + def port(*args) + self.configuration.port *args end - protected + def pid_dir(*args) + self.configuration.pid_dir *args + end - def request_handler(request) - handler_class(get_handler_class_name(request)).new(self.logger, request) + def logger(*args) + self.configuration.logger *args end - def handler_class(class_name_str) - self.logger.info(" Handler: #{class_name_str.inspect}") - Sanford::ServiceHandler.constantize(class_name_str).tap do |handler_class| - raise Sanford::NoHandlerClassError.new(self, class_name_str) if !handler_class - end + def verbose_logging(*args) + self.configuration.verbose_logging *args end - def get_handler_class_name(request) - services = self.config.versioned_services[request.version] || {} - services[request.name].tap do |name| - raise Sanford::NotFoundError if !name - end + def error(&block) + self.configuration.error_proc = block end - def remove_nil_values(options) - (options || {}).inject({}) do |hash, (k, v)| - hash.merge!({ k => v }) if !v.nil? - hash - end + def version(name, &block) + version_group = Sanford::Host::VersionGroup.new(name, &block) + @versioned_services.merge!(version_group.to_hash) end - module Interface - - INTERFACE_OPTIONS.each do |name| - - define_method(name) do |*args| - self.config.send("#{name}=", *args) if !args.empty? - self.config.send(name) - end - - define_method("#{name}=") do |new_value| - self.config.send("#{name}=", new_value) - end - - end - - def version(name, &block) - version_group = Sanford::Host::VersionGroup.new(name, &block) - self.config.versioned_services.merge!(version_group.to_hash) - end - + def inspect + reference = '0x0%x' % (self.object_id << 1) + "#<#{self.class}:#{reference} ip=#{self.configuration.ip.inspect} " \ + "port=#{self.configuration.port.inspect}>" end + protected + class VersionGroup attr_reader :name, :services def initialize(name, &definition_block) @name = name @@ -156,7 +113,28 @@ { self.name => self.services } end end + module ClassMethods + + # the class level of a `Host` should just proxy it's methods down to it's + # instance (it's a `Singleton`) + + # `name` is defined by all objects, so we can't rely on `method_missing` + def name(*args) + self.instance.name(*args) + end + + def method_missing(method, *args, &block) + self.instance.send(method, *args, &block) + end + + def respond_to?(method) + super || self.instance.respond_to?(method) + end + + end + end + end