diff --git a/lib/rest_client.rb b/lib/rest_client.rb index f7ca04b..7936dc1 100644 --- a/lib/rest_client.rb +++ b/lib/rest_client.rb @@ -4,89 +4,89 @@ require 'rexml/document' module RestClient def self.get(url, headers={}) - do_request :get, url, nil, headers + Request.new(:get, url, nil, headers).execute end def self.post(url, payload=nil, headers={}) - do_request :post, url, payload, headers + Request.new(:post, url, payload, headers).execute end def self.put(url, payload=nil, headers={}) - do_request :put, url, payload, headers + Request.new(:put, url, payload, headers).execute end def self.delete(url, headers={}) - do_request :delete, url, nil, headers + Request.new(:delete, url, nil, headers).execute end - #### + class Request + attr_reader :method, :url, :payload, :headers - def self.do_request(method, url, payload, headers) - do_request_inner(method, url, payload, headers) - rescue Redirect => e - do_request(method, e.message, payload, headers) - end - - def self.do_request_inner(method, url, payload, headers) - uri = parse_url(url) - transmit uri, net_http_class(method).new(uri.path, make_headers(headers)), payload - end - - def self.make_headers(user_headers) - final = {} - merged = default_headers.merge(user_headers) - merged.keys.each do |key| - final[key.to_s.gsub(/_/, '-').capitalize] = merged[key] + def initialize(method, url, payload, headers) + @method = method + @url = url + @payload = payload + @headers = headers end - final - end - def self.net_http_class(method) - Object.module_eval "Net::HTTP::#{method.to_s.capitalize}" - end - - def self.parse_url(url) - url = "http://#{url}" unless url.match(/^http/) - URI.parse(url) - end - - class Redirect < Exception; end - class RequestFailed < Exception; end - class Unauthorized < Exception; end - - def self.transmit(uri, req, payload) - Net::HTTP.start(uri.host, uri.port) do |http| - process_result http.request(req, payload || "") + def execute + execute_inner + rescue Redirect => e + @url = e.message + execute end - end - def self.process_result(res) - if %w(200 201 202).include? res.code - res.body - elsif %w(301 302 303).include? res.code - raise Redirect, res.header['Location'] - elsif res.code == "401" - raise Unauthorized - else - raise RequestFailed, error_message(res) + def execute_inner + uri = parse_url(url) + transmit uri, net_http_class(method).new(uri.path, make_headers(headers)), payload end - end - def self.parse_error_xml(body) - xml(body).elements.to_a("//errors/error").map { |a| a.text }.join(" / ") - rescue - "unknown error" - end + def make_headers(user_headers) + final = {} + merged = default_headers.merge(user_headers) + merged.keys.each do |key| + final[key.to_s.gsub(/_/, '-').capitalize] = merged[key] + end + final + end - def self.error_message(res) - "HTTP code #{res.code}: #{parse_error_xml(res.body)}" - end + def net_http_class(method) + Object.module_eval "Net::HTTP::#{method.to_s.capitalize}" + end - def self.default_headers - { :accept => 'application/xml', :content_type => 'text/plain' } - end + def parse_url(url) + url = "http://#{url}" unless url.match(/^http/) + URI.parse(url) + end - def self.xml(raw) - REXML::Document.new(raw) + class Redirect < Exception; end + class RequestFailed < Exception; end + class Unauthorized < Exception; end + + def transmit(uri, req, payload) + Net::HTTP.start(uri.host, uri.port) do |http| + process_result http.request(req, payload || "") + end + end + + def process_result(res) + if %w(200 201 202).include? res.code + res.body + elsif %w(301 302 303).include? res.code + raise Redirect, res.header['Location'] + elsif res.code == "401" + raise Unauthorized + else + raise RequestFailed, error_message(res) + end + end + + def error_message(res) + "HTTP code #{res.code}: #{res.body}" + end + + def default_headers + { :accept => 'application/xml' } + end end end diff --git a/spec/rest_client_spec.rb b/spec/rest_client_spec.rb index 6401d86..ed8ff5e 100644 --- a/spec/rest_client_spec.rb +++ b/spec/rest_client_spec.rb @@ -2,110 +2,113 @@ require File.dirname(__FILE__) + '/base' describe RestClient do context "public API" do + before do + @request = mock("restclient request") + end + it "GET" do - RestClient.should_receive(:do_request).with(:get, 'http://some/resource', nil, {}) + RestClient::Request.should_receive(:new).with(:get, 'http://some/resource', nil, {}).and_return(@request) + @request.should_receive(:execute) RestClient.get('http://some/resource') end it "POST" do - RestClient.should_receive(:do_request).with(:post, 'http://some/resource', 'payload', {}) + RestClient::Request.should_receive(:new).with(:post, 'http://some/resource', 'payload', {}).and_return(@request) + @request.should_receive(:execute) RestClient.post('http://some/resource', 'payload') end it "PUT" do - RestClient.should_receive(:do_request).with(:put, 'http://some/resource', 'payload', {}) + RestClient::Request.should_receive(:new).with(:put, 'http://some/resource', 'payload', {}).and_return(@request) + @request.should_receive(:execute) RestClient.put('http://some/resource', 'payload') end it "DELETE" do - RestClient.should_receive(:do_request).with(:delete, 'http://some/resource', nil, {}) + RestClient::Request.should_receive(:new).with(:delete, 'http://some/resource', nil, {}).and_return(@request) + @request.should_receive(:execute) RestClient.delete('http://some/resource') end end - context "internal support methods" do - it "requests xml mimetype" do - RestClient.default_headers[:accept].should == 'application/xml' - end - - it "converts an xml document" do - REXML::Document.should_receive(:new).with('body') - RestClient.xml('body') - end - - it "processes a successful result" do - res = mock("result") - res.stub!(:code).and_return("200") - res.stub!(:body).and_return('body') - RestClient.process_result(res).should == 'body' - end - - it "parses a url into a URI object" do - URI.should_receive(:parse).with('http://example.com/resource') - RestClient.parse_url('http://example.com/resource') - end - - it "adds http:// to the front of resources specified in the syntax example.com/resource" do - URI.should_receive(:parse).with('http://example.com/resource') - RestClient.parse_url('example.com/resource') - end - - it "determines the Net::HTTP class to instantiate by the method name" do - RestClient.net_http_class(:put).should == Net::HTTP::Put - end - - it "merges user headers with the default headers" do - RestClient.should_receive(:default_headers).and_return({ '1' => '2' }) - RestClient.make_headers('3' => '4').should == { '1' => '2', '3' => '4' } - end - - it "prefers the user header when the same header exists in the defaults" do - RestClient.should_receive(:default_headers).and_return({ '1' => '2' }) - RestClient.make_headers('1' => '3').should == { '1' => '3' } - end - - it "converts header symbols from :content_type to 'Content-type'" do - RestClient.should_receive(:default_headers).and_return({}) - RestClient.make_headers(:content_type => 'abc').should == { 'Content-type' => 'abc' } - end - end - - context "internal transmission methods" do + context RestClient::Request do before do + @request = RestClient::Request.new(:put, 'http://some/resource', 'payload', {}) + @uri = mock("uri") @uri.stub!(:path).and_return('/resource') @uri.stub!(:host).and_return('some') @uri.stub!(:port).and_return(80) end - it "does a request with an http method name passed in as a symbol" do - RestClient.should_receive(:parse_url).with('http://some/resource').and_return(@uri) + it "requests xml mimetype" do + @request.default_headers[:accept].should == 'application/xml' + end + + it "processes a successful result" do + res = mock("result") + res.stub!(:code).and_return("200") + res.stub!(:body).and_return('body') + @request.process_result(res).should == 'body' + end + + it "parses a url into a URI object" do + URI.should_receive(:parse).with('http://example.com/resource') + @request.parse_url('http://example.com/resource') + end + + it "adds http:// to the front of resources specified in the syntax example.com/resource" do + URI.should_receive(:parse).with('http://example.com/resource') + @request.parse_url('example.com/resource') + end + + it "determines the Net::HTTP class to instantiate by the method name" do + @request.net_http_class(:put).should == Net::HTTP::Put + end + + it "merges user headers with the default headers" do + @request.should_receive(:default_headers).and_return({ '1' => '2' }) + @request.make_headers('3' => '4').should == { '1' => '2', '3' => '4' } + end + + it "prefers the user header when the same header exists in the defaults" do + @request.should_receive(:default_headers).and_return({ '1' => '2' }) + @request.make_headers('1' => '3').should == { '1' => '3' } + end + + it "converts header symbols from :content_type to 'Content-type'" do + @request.should_receive(:default_headers).and_return({}) + @request.make_headers(:content_type => 'abc').should == { 'Content-type' => 'abc' } + end + + it "executes by constructing the Net::HTTP object, headers, and payload and calling transmit" do + @request.should_receive(:parse_url).with('http://some/resource').and_return(@uri) klass = mock("net:http class") - RestClient.should_receive(:net_http_class).with(:put).and_return(klass) + @request.should_receive(:net_http_class).with(:put).and_return(klass) klass.should_receive(:new).and_return('result') - RestClient.should_receive(:transmit).with(@uri, 'result', 'payload') - RestClient.do_request_inner(:put, 'http://some/resource', 'payload', {}) + @request.should_receive(:transmit).with(@uri, 'result', 'payload') + @request.execute_inner end it "transmits the request with Net::HTTP" do http = mock("net::http connection") Net::HTTP.should_receive(:start).and_yield(http) http.should_receive(:request).with('req', 'payload') - RestClient.should_receive(:process_result) - RestClient.transmit(@uri, 'req', 'payload') + @request.should_receive(:process_result) + @request.transmit(@uri, 'req', 'payload') end it "doesn't send nil payloads" do http = mock("net::http connection") Net::HTTP.should_receive(:start).and_yield(http) http.should_receive(:request).with('req', '') - RestClient.should_receive(:process_result) - RestClient.transmit(@uri, 'req', nil) + @request.should_receive(:process_result) + @request.transmit(@uri, 'req', nil) end - it "do_request calls do_request_inner" do - RestClient.should_receive(:do_request_inner).with(:get, 'http://some/where', 'payload', {}) - RestClient.do_request(:get, 'http://some/where', 'payload', {}) + it "execute calls execute_inner" do + @request.should_receive(:execute_inner) + @request.execute end end end