diff options
Diffstat (limited to 'lib/puppet')
-rw-r--r-- | lib/puppet/indirector/indirection.rb | 8 | ||||
-rw-r--r-- | lib/puppet/network/http.rb | 13 | ||||
-rw-r--r-- | lib/puppet/network/http/handler.rb | 109 | ||||
-rw-r--r-- | lib/puppet/network/http/mongrel.rb | 54 | ||||
-rw-r--r-- | lib/puppet/network/http/mongrel/rest.rb | 37 | ||||
-rw-r--r-- | lib/puppet/network/http/mongrel/xmlrpc.rb | 4 | ||||
-rw-r--r-- | lib/puppet/network/http/webrick.rb | 51 | ||||
-rw-r--r-- | lib/puppet/network/http/webrick/rest.rb | 41 | ||||
-rw-r--r-- | lib/puppet/network/http/webrick/xmlrpc.rb | 4 | ||||
-rw-r--r-- | lib/puppet/network/server.rb | 105 |
10 files changed, 373 insertions, 53 deletions
diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 9a9b1c0bf..816b4ffc5 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -13,6 +13,14 @@ class Puppet::Indirector::Indirection @@indirections.find { |i| i.name == name } end + # Find an indirected model by name. This is provided so that Terminus classes + # can specifically hook up with the indirections they are associated with. + def self.model(name) + match = @@indirections.find { |i| i.name == name } + return nil unless match + match.model + end + attr_accessor :name, :model # Create and return our cache terminus. diff --git a/lib/puppet/network/http.rb b/lib/puppet/network/http.rb new file mode 100644 index 000000000..062c67c71 --- /dev/null +++ b/lib/puppet/network/http.rb @@ -0,0 +1,13 @@ +class Puppet::Network::HTTP + def self.server_class_by_type(kind) + return Puppet::Network::HTTP::WEBrick if kind.to_sym == :webrick + if kind.to_sym == :mongrel + raise ArgumentError, "Mongrel is not installed on this platform" unless Puppet.features.mongrel? + return Puppet::Network::HTTP::Mongrel + end + raise ArgumentError, "Unknown HTTP server name [#{kind}]" + end +end + +require 'puppet/network/http/webrick' +require 'puppet/network/http/mongrel' diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb new file mode 100644 index 000000000..773381c8d --- /dev/null +++ b/lib/puppet/network/http/handler.rb @@ -0,0 +1,109 @@ +class Puppet::Network::HTTP::Handler + def initialize(args = {}) + raise ArgumentError unless @server = args[:server] + raise ArgumentError unless @handler = args[:handler] + @model = find_model_for_handler(@handler) + register_handler + end + + # handle an HTTP request + def process(request, response) + return do_find(request, response) if get?(request) and singular?(request) + return do_search(request, response) if get?(request) and plural?(request) + return do_destroy(request, response) if delete?(request) and singular?(request) + return do_save(request, response) if put?(request) and singular?(request) + raise ArgumentError, "Did not understand HTTP #{http_method(request)} request for '#{path(request)}'" + rescue Exception => e + return do_exception(request, response, e) + end + + private + + def do_find(request, response) + key = request_key(request) || raise(ArgumentError, "Could not locate lookup key in request path [#{path}]") + args = params(request) + result = @model.find(key, args).to_yaml + encode_result(request, response, result) + end + + def do_search(request, response) + args = params(request) + result = @model.search(args).collect {|obj| obj.to_yaml } + encode_result(request, response, result) + end + + def do_destroy(request, response) + key = request_key(request) || raise(ArgumentError, "Could not locate lookup key in request path [#{path}]") + args = params(request) + result = @model.destroy(key, args) + encode_result(request, response, YAML.dump(result)) + end + + def do_save(request, response) + data = body(request) + raise ArgumentError, "No data to save" if !data or data.empty? + args = params(request) + obj = @model.new + result = obj.save(args.merge(:data => data)).to_yaml + encode_result(request, response, result) + end + + def do_exception(request, response, exception, status=404) + encode_result(request, response, exception.to_s, status) + end + + def find_model_for_handler(handler) + Puppet::Indirector::Indirection.model(handler) || + raise(ArgumentError, "Cannot locate indirection [#{handler}].") + end + + def get?(request) + http_method(request) == 'GET' + end + + def put?(request) + http_method(request) == 'PUT' + end + + def delete?(request) + http_method(request) == 'DELETE' + end + + def singular?(request) + %r{/#{@handler.to_s}$}.match(path(request)) + end + + def plural?(request) + %r{/#{@handler.to_s}s$}.match(path(request)) + end + + # methods specific to a given web server + + def register_handler + raise NotImplementedError + end + + def http_method(request) + raise NotImplementedError + end + + def path(request) + raise NotImplementedError + end + + def request_key(request) + raise NotImplementedError + end + + def body(request) + raise NotImplementedError + end + + def params(request) + raise NotImplementedError + end + + def encode_result(request, response, result, status = 200) + raise NotImplementedError + end +end diff --git a/lib/puppet/network/http/mongrel.rb b/lib/puppet/network/http/mongrel.rb new file mode 100644 index 000000000..8ea669531 --- /dev/null +++ b/lib/puppet/network/http/mongrel.rb @@ -0,0 +1,54 @@ +require 'mongrel' if Puppet.features.mongrel? + +require 'puppet/network/http/mongrel/rest' +require 'puppet/network/http/mongrel/xmlrpc' + +class Puppet::Network::HTTP::Mongrel + def initialize(args = {}) + @listening = false + end + + def listen(args = {}) + raise ArgumentError, ":handlers must be specified." if !args[:handlers] or args[:handlers].empty? + raise ArgumentError, ":protocols must be specified." if !args[:protocols] or args[:protocols].empty? + raise ArgumentError, ":address must be specified." unless args[:address] + raise ArgumentError, ":port must be specified." unless args[:port] + raise "Mongrel server is already listening" if listening? + + @protocols = args[:protocols] + @handlers = args[:handlers] + @server = Mongrel::HttpServer.new(args[:address], args[:port]) + + setup_handlers + + @server.run + @listening = true + end + + def unlisten + raise "Mongrel server is not listening" unless listening? + @server.graceful_shutdown + @listening = false + end + + def listening? + @listening + end + + private + + def setup_handlers + @protocols.each do |protocol| + @handlers.each do |handler| + class_for_protocol(protocol).new(:server => @server, :handler => handler) + end + end + end + + # TODO/FIXME: need a spec which forces delegation to the real class + def class_for_protocol(protocol) + return Puppet::Network::HTTP::MongrelREST if protocol.to_sym == :rest + return Puppet::Network::HTTP::MongrelXMLRPC if protocol.to_sym == :xmlrpc + raise ArgumentError, "Unknown protocol [#{protocol}]." + end +end diff --git a/lib/puppet/network/http/mongrel/rest.rb b/lib/puppet/network/http/mongrel/rest.rb new file mode 100644 index 000000000..db63613ab --- /dev/null +++ b/lib/puppet/network/http/mongrel/rest.rb @@ -0,0 +1,37 @@ +require 'puppet/network/http/handler' + +class Puppet::Network::HTTP::MongrelREST < Puppet::Network::HTTP::Handler + + private + + def register_handler + @server.register('/' + @handler.to_s, self) + @server.register('/' + @handler.to_s + 's', self) + end + + def http_method(request) + request.params[Mongrel::Const::REQUEST_METHOD] + end + + def path(request) + '/' + request.params[Mongrel::Const::REQUEST_PATH].split('/')[1] + end + + def request_key(request) + request.params[Mongrel::Const::REQUEST_PATH].split('/')[2] + end + + def body(request) + request.body + end + + def params(request) + Mongrel::HttpRequest.query_parse(request.params["QUERY_STRING"]) + end + + def encode_result(request, response, result, status = 200) + response.start(status) do |head, body| + body.write(result) + end + end +end diff --git a/lib/puppet/network/http/mongrel/xmlrpc.rb b/lib/puppet/network/http/mongrel/xmlrpc.rb new file mode 100644 index 000000000..92acd4f0e --- /dev/null +++ b/lib/puppet/network/http/mongrel/xmlrpc.rb @@ -0,0 +1,4 @@ +class Puppet::Network::HTTP::MongrelXMLRPC + def initialize(args = {}) + end +end diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb new file mode 100644 index 000000000..c4b2ed3c6 --- /dev/null +++ b/lib/puppet/network/http/webrick.rb @@ -0,0 +1,51 @@ +require 'webrick' +require 'webrick/https' +require 'puppet/network/http/webrick/rest' +require 'puppet/network/http/webrick/xmlrpc' + +class Puppet::Network::HTTP::WEBrick + def initialize(args = {}) + @listening = false + end + + def listen(args = {}) + raise ArgumentError, ":handlers must be specified." if !args[:handlers] or args[:handlers].empty? + raise ArgumentError, ":protocols must be specified." if !args[:protocols] or args[:protocols].empty? + raise ArgumentError, ":address must be specified." unless args[:address] + raise ArgumentError, ":port must be specified." unless args[:port] + raise "WEBrick server is already listening" if listening? + + @protocols = args[:protocols] + @handlers = args[:handlers] + @server = WEBrick::HTTPServer.new(:BindAddress => args[:address], :Port => args[:port]) + setup_handlers + @server.start + @listening = true + end + + def unlisten + raise "WEBrick server is not listening" unless listening? + @server.shutdown + @listening = false + end + + def listening? + @listening + end + + private + + def setup_handlers + @protocols.each do |protocol| + @handlers.each do |handler| + class_for_protocol(protocol).new(:server => @server, :handler => handler) + end + end + end + + def class_for_protocol(protocol) + return Puppet::Network::HTTP::WEBrickREST if protocol.to_sym == :rest + return Puppet::Network::HTTP::WEBrickXMLRPC if protocol.to_sym == :xmlrpc + raise ArgumentError, "Unknown protocol [#{protocol}]." + end +end diff --git a/lib/puppet/network/http/webrick/rest.rb b/lib/puppet/network/http/webrick/rest.rb new file mode 100644 index 000000000..dd0c84d61 --- /dev/null +++ b/lib/puppet/network/http/webrick/rest.rb @@ -0,0 +1,41 @@ +require 'puppet/network/http/handler' + +class Puppet::Network::HTTP::WEBrickREST < Puppet::Network::HTTP::Handler + + # WEBrick uses a service() method to respond to requests. Simply delegate to the handler response() method. + def service(request, response) + process(request, response) + end + + private + + def register_handler + @server.mount('/' + @handler.to_s, self) + @server.mount('/' + @handler.to_s + 's', self) + end + + def http_method(request) + request.request_method + end + + def path(request) + '/' + request.path.split('/')[1] + end + + def request_key(request) + request.path.split('/')[2] + end + + def body(request) + request.body + end + + def params(request) + request.query + end + + def encode_result(request, response, result, status = 200) + response.status = status + response.body = result + end +end
\ No newline at end of file diff --git a/lib/puppet/network/http/webrick/xmlrpc.rb b/lib/puppet/network/http/webrick/xmlrpc.rb new file mode 100644 index 000000000..793708f8a --- /dev/null +++ b/lib/puppet/network/http/webrick/xmlrpc.rb @@ -0,0 +1,4 @@ +class Puppet::Network::HTTP::WEBrickXMLRPC + def initialize(args = {}) + end +end diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb index 84a71a6b4..50e3bd686 100644 --- a/lib/puppet/network/server.rb +++ b/lib/puppet/network/server.rb @@ -1,66 +1,65 @@ class Puppet::Network::Server - attr_reader :server_type + attr_reader :server_type, :protocols, :address, :port - # which HTTP server subclass actually handles web requests of a certain type? (e.g., :rest => RESTServer) - def self.server_class_by_name(name) - klass = (name.to_s + 'Server').to_sym - const_get klass - end - - # we will actually return an instance of the Server subclass which handles the HTTP web server, instead of - # an instance of this generic Server class. A tiny bit of sleight-of-hand is necessary to make this happen. - def self.new(args = {}) - server_type = Puppet[:servertype] or raise "No servertype configuration found." - obj = self.server_class_by_name(server_type).allocate - obj.send :initialize, args.merge(:server_type => server_type) - obj - end - - def initialize(args = {}) - @routes = {} - @listening = false - @server_type = args[:server_type] - self.register(args[:handlers]) if args[:handlers] - end + def initialize(args = {}) + @server_type = Puppet[:servertype] or raise "No servertype configuration found." # e.g., WEBrick, Mongrel, etc. + http_server_class || raise(ArgumentError, "Could not determine HTTP Server class for server type [#{@server_type}]") + @address = args[:address] || Puppet[:bindaddress] || + raise(ArgumentError, "Must specify :address or configure Puppet :bindaddress.") + @port = args[:port] || Puppet[:masterport] || + raise(ArgumentError, "Must specify :port or configure Puppet :masterport") + @protocols = [] + @listening = false + @routes = {} + self.register(args[:handlers]) if args[:handlers] + end - def register(*indirections) - raise ArgumentError, "indirection names are required" if indirections.empty? - indirections.flatten.each { |i| @routes[i.to_sym] = true } - end + def register(*indirections) + raise ArgumentError, "Indirection names are required." if indirections.empty? + indirections.flatten.each { |i| @routes[i.to_sym] = true } + end - def unregister(*indirections) - indirections = @routes.keys if indirections.empty? - indirections.flatten.each do |i| - raise(ArgumentError, "indirection [%s] is not known" % i) unless @routes[i.to_sym] - @routes.delete(i.to_sym) + def unregister(*indirections) + raise "Cannot unregister indirections while server is listening." if listening? + indirections = @routes.keys if indirections.empty? + + indirections.flatten.each do |i| + raise(ArgumentError, "Indirection [%s] is unknown." % i) unless @routes[i.to_sym] + end + + indirections.flatten.each do |i| + @routes.delete(i.to_sym) + end end - end - def listening? - @listening - end + def listening? + @listening + end - def listen - raise "Cannot listen -- already listening" if listening? - start_web_server - @listening = true - end + def listen + raise "Cannot listen -- already listening." if listening? + http_server.listen(@routes.dup) + @listening = true + end - def unlisten - raise "Cannot unlisten -- not currently listening" unless listening? - stop_web_server - @listening = false - end + def unlisten + raise "Cannot unlisten -- not currently listening." unless listening? + http_server.unlisten + @listening = false + end + + def http_server_class + http_server_class_by_type(@server_type) + end private - def start_web_server - raise NotImplementedError, "this method needs to be implemented by the actual web server (sub)class" - end - - def stop_web_server - raise NotImplementedError, "this method needs to be implemented by the actual web server (sub)class" - end + def http_server + @http_server ||= http_server_class.new + end + + def http_server_class_by_type(kind) + Puppet::Network::HTTP.server_class_by_type(kind) + end end - |