diff --git a/lib/puppet/network/http/route.rb b/lib/puppet/network/http/route.rb index e73d5b593..81f311616 100644 --- a/lib/puppet/network/http/route.rb +++ b/lib/puppet/network/http/route.rb @@ -1,91 +1,100 @@ class Puppet::Network::HTTP::Route MethodNotAllowedHandler = lambda do |req, res| raise Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError.new("method #{req.method} not allowed for route #{req.path}", Puppet::Network::HTTP::Issues::UNSUPPORTED_METHOD) end + NO_HANDLERS = [MethodNotAllowedHandler] + attr_reader :path_matcher def self.path(path_matcher) new(path_matcher) end def initialize(path_matcher) @path_matcher = path_matcher @method_handlers = { - :GET => [MethodNotAllowedHandler], - :HEAD => [MethodNotAllowedHandler], - :OPTIONS => [MethodNotAllowedHandler], - :POST => [MethodNotAllowedHandler], - :PUT => [MethodNotAllowedHandler] + :GET => NO_HANDLERS, + :HEAD => NO_HANDLERS, + :OPTIONS => NO_HANDLERS, + :POST => NO_HANDLERS, + :PUT => NO_HANDLERS, + :DELETE => NO_HANDLERS } @chained = [] end def get(*handlers) @method_handlers[:GET] = handlers return self end def head(*handlers) @method_handlers[:HEAD] = handlers return self end def options(*handlers) @method_handlers[:OPTIONS] = handlers return self end def post(*handlers) @method_handlers[:POST] = handlers return self end def put(*handlers) @method_handlers[:PUT] = handlers return self end + def delete(*handlers) + @method_handlers[:DELETE] = handlers + return self + end + def any(*handlers) @method_handlers.each do |method, registered_handlers| @method_handlers[method] = handlers end return self end def chain(*routes) @chained = routes self end def matches?(request) Puppet.debug("Evaluating match for #{self.inspect}") if match(request.routing_path) return true else Puppet.debug("Did not match path (#{request.routing_path.inspect})") end return false end def process(request, response) - @method_handlers[request.method.upcase.intern].each do |handler| + handlers = @method_handlers[request.method.upcase.intern] || NO_HANDLERS + handlers.each do |handler| handler.call(request, response) end subrequest = request.route_into(match(request.routing_path).to_s) if chained_route = @chained.find { |route| route.matches?(subrequest) } chained_route.process(subrequest, response) end end def inspect "Route #{@path_matcher.inspect}" end private def match(path) @path_matcher.match(path) end end diff --git a/spec/unit/network/http/route_spec.rb b/spec/unit/network/http/route_spec.rb index 04cc0e875..04f8f8388 100644 --- a/spec/unit/network/http/route_spec.rb +++ b/spec/unit/network/http/route_spec.rb @@ -1,75 +1,91 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector_testing' require 'puppet/network/http' describe Puppet::Network::HTTP::Route do def request(method, path) Puppet::Network::HTTP::Request.from_hash({ :method => method, :path => path, :routing_path => path }) end def respond(text) lambda { |req, res| res.respond_with(200, "text/plain", text) } end let(:req) { request("GET", "/vtest/foo") } let(:res) { Puppet::Network::HTTP::MemoryResponse.new } describe "an HTTP Route" do it "can match a request" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest}) expect(route.matches?(req)).to be_true end it "will raise a Method Not Allowed error when no handler for the request's method is given" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest}).post(respond("ignored")) expect do route.process(req, res) end.to raise_error(Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError) end it "can match any HTTP method" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).any(respond("used")) expect(route.matches?(req)).to be_true route.process(req, res) expect(res.body).to eq("used") end + it "processes DELETE requests" do + route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).delete(respond("used")) + + route.process(request("DELETE", "/vtest/foo"), res) + + expect(res.body).to eq("used") + end + + it "does something when it doesn't know the verb" do + route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}) + + expect do + route.process(request("UNKNOWN", "/vtest/foo"), res) + end.to raise_error(Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError, /UNKNOWN/) + end + it "calls the method handlers in turn" do call_count = 0 handler = lambda { |request, response| call_count += 1 } route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).get(handler, handler) route.process(req, res) expect(call_count).to eq(2) end it "stops calling handlers if one of them raises an error" do ignored_called = false ignored = lambda { |req, res| ignored_called = true } raise_error = lambda { |req, res| raise Puppet::Network::HTTP::Error::HTTPNotAuthorizedError, "go away" } route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).get(raise_error, ignored) expect do route.process(req, res) end.to raise_error(Puppet::Network::HTTP::Error::HTTPNotAuthorizedError) expect(ignored_called).to be_false end it "chains to other routes after calling its handlers" do inner_route = Puppet::Network::HTTP::Route.path(%r{^/inner}).any(respond("inner")) unused_inner_route = Puppet::Network::HTTP::Route.path(%r{^/unused_inner}).any(respond("unused")) top_route = Puppet::Network::HTTP::Route.path(%r{^/vtest}).any(respond("top")).chain(unused_inner_route, inner_route) top_route.process(request("GET", "/vtest/inner"), res) expect(res.body).to eq("topinner") end end end