mirror of
https://github.com/rest-client/rest-client.git
synced 2022-11-09 13:49:40 -05:00
Merge branch 'master' into prereorg
Conflicts: lib/rest_client.rb lib/restclient/resource.rb rest-client.gemspec spec/rest_client_spec.rb
This commit is contained in:
commit
3425532660
23 changed files with 1182 additions and 754 deletions
69
Rakefile
69
Rakefile
|
@ -1,4 +1,24 @@
|
||||||
require 'rake'
|
require 'rake'
|
||||||
|
|
||||||
|
require 'jeweler'
|
||||||
|
|
||||||
|
Jeweler::Tasks.new do |s|
|
||||||
|
s.name = "rest-client"
|
||||||
|
s.description = "A simple REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete."
|
||||||
|
s.summary = "Simple REST client for Ruby, inspired by microframework syntax for specifying actions."
|
||||||
|
s.author = "Adam Wiggins"
|
||||||
|
s.email = "adam@heroku.com"
|
||||||
|
s.homepage = "http://rest-client.heroku.com/"
|
||||||
|
s.rubyforge_project = "rest-client"
|
||||||
|
s.has_rdoc = true
|
||||||
|
s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
|
||||||
|
s.executables = %w(restclient)
|
||||||
|
end
|
||||||
|
|
||||||
|
Jeweler::RubyforgeTasks.new
|
||||||
|
|
||||||
|
############################
|
||||||
|
|
||||||
require 'spec/rake/spectask'
|
require 'spec/rake/spectask'
|
||||||
|
|
||||||
desc "Run all specs"
|
desc "Run all specs"
|
||||||
|
@ -22,54 +42,9 @@ end
|
||||||
|
|
||||||
task :default => :spec
|
task :default => :spec
|
||||||
|
|
||||||
######################################################
|
############################
|
||||||
|
|
||||||
require 'rake'
|
|
||||||
require 'rake/testtask'
|
|
||||||
require 'rake/clean'
|
|
||||||
require 'rake/gempackagetask'
|
|
||||||
require 'rake/rdoctask'
|
require 'rake/rdoctask'
|
||||||
require 'fileutils'
|
|
||||||
|
|
||||||
version = "0.8.2"
|
|
||||||
name = "rest-client"
|
|
||||||
|
|
||||||
spec = Gem::Specification.new do |s|
|
|
||||||
s.name = name
|
|
||||||
s.version = version
|
|
||||||
s.summary = "Simple REST client for Ruby, inspired by microframework syntax for specifying actions."
|
|
||||||
s.description = "A simple REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete."
|
|
||||||
s.author = "Adam Wiggins"
|
|
||||||
s.email = "adam@heroku.com"
|
|
||||||
s.homepage = "http://rest-client.heroku.com/"
|
|
||||||
s.rubyforge_project = "rest-client"
|
|
||||||
|
|
||||||
s.platform = Gem::Platform::RUBY
|
|
||||||
s.has_rdoc = true
|
|
||||||
|
|
||||||
s.files = %w(Rakefile) + Dir.glob("{lib,spec}/**/*")
|
|
||||||
s.executables = ['restclient']
|
|
||||||
|
|
||||||
s.require_path = "lib"
|
|
||||||
end
|
|
||||||
|
|
||||||
Rake::GemPackageTask.new(spec) do |p|
|
|
||||||
p.need_tar = true if RUBY_PLATFORM !~ /mswin/
|
|
||||||
end
|
|
||||||
|
|
||||||
task :install => [ :package ] do
|
|
||||||
sh %{sudo gem install pkg/#{name}-#{version}.gem}
|
|
||||||
end
|
|
||||||
|
|
||||||
task :uninstall => [ :clean ] do
|
|
||||||
sh %{sudo gem uninstall #{name}}
|
|
||||||
end
|
|
||||||
|
|
||||||
Rake::TestTask.new do |t|
|
|
||||||
t.libs << "spec"
|
|
||||||
t.test_files = FileList['spec/*_spec.rb']
|
|
||||||
t.verbose = true
|
|
||||||
end
|
|
||||||
|
|
||||||
Rake::RDocTask.new do |t|
|
Rake::RDocTask.new do |t|
|
||||||
t.rdoc_dir = 'rdoc'
|
t.rdoc_dir = 'rdoc'
|
||||||
|
@ -80,5 +55,3 @@ Rake::RDocTask.new do |t|
|
||||||
t.rdoc_files.include('lib/*.rb')
|
t.rdoc_files.include('lib/*.rb')
|
||||||
end
|
end
|
||||||
|
|
||||||
CLEAN.include [ 'pkg', '*.gem', '.config' ]
|
|
||||||
|
|
||||||
|
|
1
VERSION
Normal file
1
VERSION
Normal file
|
@ -0,0 +1 @@
|
||||||
|
1.2.1
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
$:.unshift File.dirname(__FILE__) + "/../lib"
|
$:.unshift File.dirname(__FILE__) + "/../lib"
|
||||||
require 'rest_client'
|
require 'restclient'
|
||||||
|
|
||||||
require "yaml"
|
require "yaml"
|
||||||
|
|
||||||
|
|
|
@ -1,293 +1,2 @@
|
||||||
require 'uri'
|
# This file exists for backward compatbility with require 'rest_client'
|
||||||
require 'net/https'
|
require File.dirname(__FILE__) + '/restclient'
|
||||||
require 'zlib'
|
|
||||||
require 'stringio'
|
|
||||||
|
|
||||||
require File.dirname(__FILE__) + '/rest_client/resource'
|
|
||||||
require File.dirname(__FILE__) + '/rest_client/request_errors'
|
|
||||||
require File.dirname(__FILE__) + '/rest_client/payload'
|
|
||||||
require File.dirname(__FILE__) + '/rest_client/net_http_ext'
|
|
||||||
|
|
||||||
# This module's static methods are the entry point for using the REST client.
|
|
||||||
#
|
|
||||||
# # GET
|
|
||||||
# xml = RestClient.get 'http://example.com/resource'
|
|
||||||
# jpg = RestClient.get 'http://example.com/resource', :accept => 'image/jpg'
|
|
||||||
#
|
|
||||||
# # authentication and SSL
|
|
||||||
# RestClient.get 'https://user:password@example.com/private/resource'
|
|
||||||
#
|
|
||||||
# # POST or PUT with a hash sends parameters as a urlencoded form body
|
|
||||||
# RestClient.post 'http://example.com/resource', :param1 => 'one'
|
|
||||||
#
|
|
||||||
# # nest hash parameters
|
|
||||||
# RestClient.post 'http://example.com/resource', :nested => { :param1 => 'one' }
|
|
||||||
#
|
|
||||||
# # POST and PUT with raw payloads
|
|
||||||
# RestClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
|
|
||||||
# RestClient.post 'http://example.com/resource.xml', xml_doc
|
|
||||||
# RestClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
|
|
||||||
#
|
|
||||||
# # DELETE
|
|
||||||
# RestClient.delete 'http://example.com/resource'
|
|
||||||
#
|
|
||||||
# To use with a proxy, just set RestClient.proxy to the proper http proxy:
|
|
||||||
#
|
|
||||||
# RestClient.proxy = "http://proxy.example.com/"
|
|
||||||
#
|
|
||||||
# Or inherit the proxy from the environment:
|
|
||||||
#
|
|
||||||
# RestClient.proxy = ENV['http_proxy']
|
|
||||||
#
|
|
||||||
# For live tests of RestClient, try using http://rest-test.heroku.com, which echoes back information about the rest call:
|
|
||||||
#
|
|
||||||
# >> RestClient.put 'http://rest-test.heroku.com/resource', :foo => 'baz'
|
|
||||||
# => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
|
|
||||||
#
|
|
||||||
module RestClient
|
|
||||||
def self.get(url, headers={}, &b)
|
|
||||||
Request.execute(:method => :get,
|
|
||||||
:url => url,
|
|
||||||
:headers => headers, &b)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.post(url, payload, headers={}, &b)
|
|
||||||
Request.execute(:method => :post,
|
|
||||||
:url => url,
|
|
||||||
:payload => payload,
|
|
||||||
:headers => headers, &b)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.put(url, payload, headers={}, &b)
|
|
||||||
Request.execute(:method => :put,
|
|
||||||
:url => url,
|
|
||||||
:payload => payload,
|
|
||||||
:headers => headers, &b)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.delete(url, headers={}, &b)
|
|
||||||
Request.execute(:method => :delete,
|
|
||||||
:url => url,
|
|
||||||
:headers => headers, &b)
|
|
||||||
end
|
|
||||||
|
|
||||||
class <<self
|
|
||||||
attr_accessor :proxy
|
|
||||||
end
|
|
||||||
|
|
||||||
# Print log of RestClient calls. Value can be stdout, stderr, or a filename.
|
|
||||||
# You can also configure logging by the environment variable RESTCLIENT_LOG.
|
|
||||||
def self.log=(log)
|
|
||||||
@@log = log
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.log # :nodoc:
|
|
||||||
return ENV['RESTCLIENT_LOG'] if ENV['RESTCLIENT_LOG']
|
|
||||||
return @@log if defined? @@log
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Internal class used to build and execute the request.
|
|
||||||
class Request
|
|
||||||
attr_reader :method, :url, :payload, :headers, :user, :password, :timeout
|
|
||||||
|
|
||||||
def self.execute(args, &b)
|
|
||||||
new(args).execute(&b)
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(args)
|
|
||||||
@method = args[:method] or raise ArgumentError, "must pass :method"
|
|
||||||
@url = args[:url] or raise ArgumentError, "must pass :url"
|
|
||||||
@payload = Payload.generate(args[:payload] || '')
|
|
||||||
@headers = args[:headers] || {}
|
|
||||||
@user = args[:user]
|
|
||||||
@password = args[:password]
|
|
||||||
@timeout = args[:timeout]
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute(&b)
|
|
||||||
execute_inner(&b)
|
|
||||||
rescue Redirect => e
|
|
||||||
@url = e.url
|
|
||||||
execute(&b)
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute_inner(&b)
|
|
||||||
uri = parse_url_with_auth(url)
|
|
||||||
transmit(uri, net_http_request_class(method).new(uri.request_uri, make_headers(headers)), payload, &b)
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_headers(user_headers)
|
|
||||||
default_headers.merge(user_headers).inject({}) do |final, (key, value)|
|
|
||||||
final[key.to_s.gsub(/_/, '-').capitalize] = value.to_s
|
|
||||||
final
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def net_http_class
|
|
||||||
if RestClient.proxy
|
|
||||||
proxy_uri = URI.parse(RestClient.proxy)
|
|
||||||
Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
|
|
||||||
else
|
|
||||||
Net::HTTP
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def net_http_request_class(method)
|
|
||||||
Net::HTTP.const_get(method.to_s.capitalize)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_url(url)
|
|
||||||
url = "http://#{url}" unless url.match(/^http/)
|
|
||||||
URI.parse(url)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_url_with_auth(url)
|
|
||||||
uri = parse_url(url)
|
|
||||||
@user = uri.user if uri.user
|
|
||||||
@password = uri.password if uri.password
|
|
||||||
uri
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_payload(p=nil, parent_key=nil)
|
|
||||||
unless p.is_a?(Hash)
|
|
||||||
p
|
|
||||||
else
|
|
||||||
@headers[:content_type] ||= 'application/x-www-form-urlencoded'
|
|
||||||
p.keys.map do |k|
|
|
||||||
key = parent_key ? "#{parent_key}[#{k}]" : k
|
|
||||||
if p[k].is_a? Hash
|
|
||||||
process_payload(p[k], key)
|
|
||||||
else
|
|
||||||
value = URI.escape(p[k].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
|
||||||
"#{key}=#{value}"
|
|
||||||
end
|
|
||||||
end.join("&")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def transmit(uri, req, payload, &b)
|
|
||||||
setup_credentials(req)
|
|
||||||
|
|
||||||
net = net_http_class.new(uri.host, uri.port)
|
|
||||||
net.use_ssl = uri.is_a?(URI::HTTPS)
|
|
||||||
net.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
||||||
|
|
||||||
display_log request_log
|
|
||||||
|
|
||||||
net.start do |http|
|
|
||||||
http.read_timeout = @timeout if @timeout
|
|
||||||
res = http.request(req, payload)
|
|
||||||
display_log response_log(res)
|
|
||||||
## Ok. I know this is weird but it's a hack for now
|
|
||||||
## this lets process_result determine if it should read the body
|
|
||||||
## into memory or not
|
|
||||||
string = process_result(http.request(req, payload || "", &b), &b)
|
|
||||||
if string
|
|
||||||
Response.new(string, res)
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rescue EOFError
|
|
||||||
raise RestClient::ServerBrokeConnection
|
|
||||||
rescue Timeout::Error
|
|
||||||
raise RestClient::RequestTimeout
|
|
||||||
ensure
|
|
||||||
payload.close
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup_credentials(req)
|
|
||||||
req.basic_auth(user, password) if user
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_result(res, &b)
|
|
||||||
if %w(200 201 202).include? res.code
|
|
||||||
decode(res['content-encoding'], res.body) unless b
|
|
||||||
elsif %w(301 302 303).include? res.code
|
|
||||||
url = res.header['Location']
|
|
||||||
|
|
||||||
if url !~ /^http/
|
|
||||||
uri = URI.parse(@url)
|
|
||||||
uri.path = "/#{url}".squeeze('/')
|
|
||||||
url = uri.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
raise Redirect, url
|
|
||||||
elsif res.code == "304"
|
|
||||||
raise NotModified
|
|
||||||
elsif res.code == "401"
|
|
||||||
raise Unauthorized, res
|
|
||||||
elsif res.code == "404"
|
|
||||||
raise ResourceNotFound, res
|
|
||||||
else
|
|
||||||
raise RequestFailed, res
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def payload
|
|
||||||
@payload
|
|
||||||
|
|
||||||
def decode(content_encoding, body)
|
|
||||||
if content_encoding == 'gzip'
|
|
||||||
Zlib::GzipReader.new(StringIO.new(body)).read
|
|
||||||
elsif content_encoding == 'deflate'
|
|
||||||
Zlib::Inflate.new.inflate(body)
|
|
||||||
else
|
|
||||||
body
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def request_log
|
|
||||||
out = []
|
|
||||||
out << "RestClient.#{method} #{url.inspect}"
|
|
||||||
out << (payload.size > 100 ? "(#{payload.size} byte payload)".inspect : payload.inspect) if payload
|
|
||||||
out << headers.inspect.gsub(/^\{/, '').gsub(/\}$/, '') unless headers.empty?
|
|
||||||
out.join(', ')
|
|
||||||
end
|
|
||||||
|
|
||||||
def response_log(res)
|
|
||||||
"# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{(res.body) ? res.body.size : nil} bytes"
|
|
||||||
end
|
|
||||||
|
|
||||||
def display_log(msg)
|
|
||||||
return unless log_to = RestClient.log
|
|
||||||
|
|
||||||
if log_to == 'stdout'
|
|
||||||
STDOUT.puts msg
|
|
||||||
elsif log_to == 'stderr'
|
|
||||||
STDERR.puts msg
|
|
||||||
else
|
|
||||||
File.open(log_to, 'a') { |f| f.puts msg }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_headers
|
|
||||||
@payload.headers.merge({ :accept => 'application/xml', :accept_encoding => 'gzip, deflate' })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Response < String
|
|
||||||
attr_reader :net_http_res
|
|
||||||
|
|
||||||
def initialize(string, net_http_res)
|
|
||||||
@net_http_res = net_http_res
|
|
||||||
super string
|
|
||||||
end
|
|
||||||
|
|
||||||
def code
|
|
||||||
@code ||= @net_http_res.code.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
def headers
|
|
||||||
@headers ||= self.class.beautify_headers(@net_http_res.to_hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.beautify_headers(headers)
|
|
||||||
headers.inject({}) do |out, (key, value)|
|
|
||||||
out[key.gsub(/-/, '_').to_sym] = value.first
|
|
||||||
out
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
101
lib/restclient.rb
Normal file
101
lib/restclient.rb
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
require 'uri'
|
||||||
|
require 'zlib'
|
||||||
|
require 'stringio'
|
||||||
|
|
||||||
|
begin
|
||||||
|
require 'net/https'
|
||||||
|
rescue LoadError => e
|
||||||
|
raise e unless RUBY_PLATFORM =~ /linux/
|
||||||
|
raise LoadError, "no such file to load -- net/https. Try running apt-get install libopenssl-ruby"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.dirname(__FILE__) + '/restclient/request'
|
||||||
|
require File.dirname(__FILE__) + '/restclient/mixin/response'
|
||||||
|
require File.dirname(__FILE__) + '/restclient/response'
|
||||||
|
require File.dirname(__FILE__) + '/restclient/raw_response'
|
||||||
|
require File.dirname(__FILE__) + '/restclient/resource'
|
||||||
|
require File.dirname(__FILE__) + '/restclient/exceptions'
|
||||||
|
require File.dirname(__FILE__) + '/restclient/payload'
|
||||||
|
require File.dirname(__FILE__) + '/restclient/net_http_ext'
|
||||||
|
|
||||||
|
# This module's static methods are the entry point for using the REST client.
|
||||||
|
#
|
||||||
|
# # GET
|
||||||
|
# xml = RestClient.get 'http://example.com/resource'
|
||||||
|
# jpg = RestClient.get 'http://example.com/resource', :accept => 'image/jpg'
|
||||||
|
#
|
||||||
|
# # authentication and SSL
|
||||||
|
# RestClient.get 'https://user:password@example.com/private/resource'
|
||||||
|
#
|
||||||
|
# # POST or PUT with a hash sends parameters as a urlencoded form body
|
||||||
|
# RestClient.post 'http://example.com/resource', :param1 => 'one'
|
||||||
|
#
|
||||||
|
# # nest hash parameters
|
||||||
|
# RestClient.post 'http://example.com/resource', :nested => { :param1 => 'one' }
|
||||||
|
#
|
||||||
|
# # POST and PUT with raw payloads
|
||||||
|
# RestClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
|
||||||
|
# RestClient.post 'http://example.com/resource.xml', xml_doc
|
||||||
|
# RestClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
|
||||||
|
#
|
||||||
|
# # DELETE
|
||||||
|
# RestClient.delete 'http://example.com/resource'
|
||||||
|
#
|
||||||
|
# # retreive the response http code and headers
|
||||||
|
# res = RestClient.get 'http://example.com/some.jpg'
|
||||||
|
# res.code # => 200
|
||||||
|
# res.headers[:content_type] # => 'image/jpg'
|
||||||
|
#
|
||||||
|
# # HEAD
|
||||||
|
# RestClient.head('http://example.com').headers
|
||||||
|
#
|
||||||
|
# To use with a proxy, just set RestClient.proxy to the proper http proxy:
|
||||||
|
#
|
||||||
|
# RestClient.proxy = "http://proxy.example.com/"
|
||||||
|
#
|
||||||
|
# Or inherit the proxy from the environment:
|
||||||
|
#
|
||||||
|
# RestClient.proxy = ENV['http_proxy']
|
||||||
|
#
|
||||||
|
# For live tests of RestClient, try using http://rest-test.heroku.com, which echoes back information about the rest call:
|
||||||
|
#
|
||||||
|
# >> RestClient.put 'http://rest-test.heroku.com/resource', :foo => 'baz'
|
||||||
|
# => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
|
||||||
|
#
|
||||||
|
module RestClient
|
||||||
|
def self.get(url, headers={})
|
||||||
|
Request.execute(:method => :get, :url => url, :headers => headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.post(url, payload, headers={})
|
||||||
|
Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.put(url, payload, headers={})
|
||||||
|
Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.delete(url, headers={})
|
||||||
|
Request.execute(:method => :delete, :url => url, :headers => headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.head(url, headers={})
|
||||||
|
Request.execute(:method => :head, :url => url, :headers => headers)
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
attr_accessor :proxy
|
||||||
|
end
|
||||||
|
|
||||||
|
# Print log of RestClient calls. Value can be stdout, stderr, or a filename.
|
||||||
|
# You can also configure logging by the environment variable RESTCLIENT_LOG.
|
||||||
|
def self.log=(log)
|
||||||
|
@@log = log
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.log # :nodoc:
|
||||||
|
return ENV['RESTCLIENT_LOG'] if ENV['RESTCLIENT_LOG']
|
||||||
|
return @@log if defined? @@log
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
|
@ -18,6 +18,10 @@ module RestClient
|
||||||
def http_code
|
def http_code
|
||||||
@response.code.to_i if @response
|
@response.code.to_i if @response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def http_body
|
||||||
|
RestClient::Request.decode(@response['content-encoding'], @response.body) if @response
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# A redirect was encountered; caught by execute to retry with the new url.
|
# A redirect was encountered; caught by execute to retry with the new url.
|
||||||
|
@ -30,7 +34,7 @@ module RestClient
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class NotModified < Exception
|
class NotModified < ExceptionWithResponse
|
||||||
ErrorMessage = 'NotModified'
|
ErrorMessage = 'NotModified'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -44,7 +48,9 @@ module RestClient
|
||||||
ErrorMessage = 'Resource not found'
|
ErrorMessage = 'Resource not found'
|
||||||
end
|
end
|
||||||
|
|
||||||
# The server broke the connection prior to the request completing.
|
# The server broke the connection prior to the request completing. Usually
|
||||||
|
# this means it crashed, or sometimes that your network connection was
|
||||||
|
# severed before it could complete.
|
||||||
class ServerBrokeConnection < Exception
|
class ServerBrokeConnection < Exception
|
||||||
ErrorMessage = 'Server broke connection'
|
ErrorMessage = 'Server broke connection'
|
||||||
end
|
end
|
43
lib/restclient/mixin/response.rb
Normal file
43
lib/restclient/mixin/response.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
module RestClient
|
||||||
|
module Mixin
|
||||||
|
module Response
|
||||||
|
attr_reader :net_http_res
|
||||||
|
|
||||||
|
# HTTP status code, always 200 since RestClient throws exceptions for
|
||||||
|
# other codes.
|
||||||
|
def code
|
||||||
|
@code ||= @net_http_res.code.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
# A hash of the headers, beautified with symbols and underscores.
|
||||||
|
# e.g. "Content-type" will become :content_type.
|
||||||
|
def headers
|
||||||
|
@headers ||= self.class.beautify_headers(@net_http_res.to_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Hash of cookies extracted from response headers
|
||||||
|
def cookies
|
||||||
|
@cookies ||= (self.headers[:set_cookie] || "").split('; ').inject({}) do |out, raw_c|
|
||||||
|
key, val = raw_c.split('=')
|
||||||
|
unless %w(expires domain path secure).member?(key)
|
||||||
|
out[key] = val
|
||||||
|
end
|
||||||
|
out
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.included(receiver)
|
||||||
|
receiver.extend(RestClient::Mixin::Response::ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def beautify_headers(headers)
|
||||||
|
headers.inject({}) do |out, (key, value)|
|
||||||
|
out[key.gsub(/-/, '_').to_sym] = value.first
|
||||||
|
out
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,14 +8,24 @@ module RestClient
|
||||||
def generate(params)
|
def generate(params)
|
||||||
if params.is_a?(String)
|
if params.is_a?(String)
|
||||||
Base.new(params)
|
Base.new(params)
|
||||||
elsif params.delete(:multipart) == true ||
|
elsif params.delete(:multipart) == true || has_file?(params)
|
||||||
params.any? { |_,v| v.respond_to?(:path) && v.respond_to?(:read) }
|
|
||||||
Multipart.new(params)
|
Multipart.new(params)
|
||||||
else
|
else
|
||||||
UrlEncoded.new(params)
|
UrlEncoded.new(params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def has_file?(params)
|
||||||
|
params.any? do |_, v|
|
||||||
|
case v
|
||||||
|
when Hash
|
||||||
|
has_file?(v)
|
||||||
|
else
|
||||||
|
v.respond_to?(:path) && v.respond_to?(:read)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Base
|
class Base
|
||||||
def initialize(params)
|
def initialize(params)
|
||||||
build_stream(params)
|
build_stream(params)
|
||||||
|
@ -92,7 +102,7 @@ module RestClient
|
||||||
|
|
||||||
def create_file_field(s, k, v)
|
def create_file_field(s, k, v)
|
||||||
begin
|
begin
|
||||||
s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"; filename=\"#{v.path}\"#{EOL}")
|
s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"; filename=\"#{File.basename(v.path)}\"#{EOL}")
|
||||||
s.write("Content-Type: #{mime_for(v.path)}#{EOL}")
|
s.write("Content-Type: #{mime_for(v.path)}#{EOL}")
|
||||||
s.write(EOL)
|
s.write(EOL)
|
||||||
while data = v.read(8124)
|
while data = v.read(8124)
|
30
lib/restclient/raw_response.rb
Normal file
30
lib/restclient/raw_response.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
require File.dirname(__FILE__) + '/mixin/response'
|
||||||
|
|
||||||
|
module RestClient
|
||||||
|
# The response from RestClient on a raw request looks like a string, but is
|
||||||
|
# actually one of these. 99% of the time you're making a rest call all you
|
||||||
|
# care about is the body, but on the occassion you want to fetch the
|
||||||
|
# headers you can:
|
||||||
|
#
|
||||||
|
# RestClient.get('http://example.com').headers[:content_type]
|
||||||
|
#
|
||||||
|
# In addition, if you do not use the response as a string, you can access
|
||||||
|
# a Tempfile object at res.file, which contains the path to the raw
|
||||||
|
# downloaded request body.
|
||||||
|
class RawResponse
|
||||||
|
include RestClient::Mixin::Response
|
||||||
|
|
||||||
|
attr_reader :file
|
||||||
|
|
||||||
|
def initialize(tempfile, net_http_res)
|
||||||
|
@net_http_res = net_http_res
|
||||||
|
@file = tempfile
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
@file.open
|
||||||
|
@file.read
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
238
lib/restclient/request.rb
Normal file
238
lib/restclient/request.rb
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
require 'tempfile'
|
||||||
|
|
||||||
|
module RestClient
|
||||||
|
# This class is used internally by RestClient to send the request, but you can also
|
||||||
|
# access it internally if you'd like to use a method not directly supported by the
|
||||||
|
# main API. For example:
|
||||||
|
#
|
||||||
|
# RestClient::Request.execute(:method => :head, :url => 'http://example.com')
|
||||||
|
#
|
||||||
|
class Request
|
||||||
|
attr_reader :method, :url, :payload, :headers,
|
||||||
|
:cookies, :user, :password, :timeout, :open_timeout,
|
||||||
|
:verify_ssl, :ssl_client_cert, :ssl_client_key, :ssl_ca_file,
|
||||||
|
:raw_response
|
||||||
|
|
||||||
|
def self.execute(args)
|
||||||
|
new(args).execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(args)
|
||||||
|
@method = args[:method] or raise ArgumentError, "must pass :method"
|
||||||
|
@url = args[:url] or raise ArgumentError, "must pass :url"
|
||||||
|
@headers = args[:headers] || {}
|
||||||
|
@cookies = @headers.delete(:cookies) || args[:cookies] || {}
|
||||||
|
@payload = process_payload(args[:payload])
|
||||||
|
@user = args[:user]
|
||||||
|
@password = args[:password]
|
||||||
|
@timeout = args[:timeout]
|
||||||
|
@open_timeout = args[:open_timeout]
|
||||||
|
@raw_response = args[:raw_response] || false
|
||||||
|
@verify_ssl = args[:verify_ssl] || false
|
||||||
|
@ssl_client_cert = args[:ssl_client_cert] || nil
|
||||||
|
@ssl_client_key = args[:ssl_client_key] || nil
|
||||||
|
@ssl_ca_file = args[:ssl_ca_file] || nil
|
||||||
|
@tf = nil # If you are a raw request, this is your tempfile
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
execute_inner
|
||||||
|
rescue Redirect => e
|
||||||
|
@url = e.url
|
||||||
|
@method = :get
|
||||||
|
@payload = nil
|
||||||
|
execute
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute_inner
|
||||||
|
uri = parse_url_with_auth(url)
|
||||||
|
transmit uri, net_http_request_class(method).new(uri.request_uri, make_headers(headers)), payload
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_headers(user_headers)
|
||||||
|
unless @cookies.empty?
|
||||||
|
user_headers[:cookie] = @cookies.map {|key, val| "#{key.to_s}=#{val}" }.join('; ')
|
||||||
|
end
|
||||||
|
|
||||||
|
default_headers.merge(user_headers).inject({}) do |final, (key, value)|
|
||||||
|
final[key.to_s.gsub(/_/, '-').capitalize] = value.to_s
|
||||||
|
final
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def net_http_class
|
||||||
|
if RestClient.proxy
|
||||||
|
proxy_uri = URI.parse(RestClient.proxy)
|
||||||
|
Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
|
||||||
|
else
|
||||||
|
Net::HTTP
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def net_http_request_class(method)
|
||||||
|
Net::HTTP.const_get(method.to_s.capitalize)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_url(url)
|
||||||
|
url = "http://#{url}" unless url.match(/^http/)
|
||||||
|
URI.parse(url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_url_with_auth(url)
|
||||||
|
uri = parse_url(url)
|
||||||
|
@user = uri.user if uri.user
|
||||||
|
@password = uri.password if uri.password
|
||||||
|
uri
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_payload(p=nil, parent_key=nil)
|
||||||
|
unless p.is_a?(Hash)
|
||||||
|
p
|
||||||
|
else
|
||||||
|
@headers[:content_type] ||= 'application/x-www-form-urlencoded'
|
||||||
|
p.keys.map do |k|
|
||||||
|
key = parent_key ? "#{parent_key}[#{k}]" : k
|
||||||
|
if p[k].is_a? Hash
|
||||||
|
process_payload(p[k], key)
|
||||||
|
else
|
||||||
|
value = URI.escape(p[k].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
||||||
|
"#{key}=#{value}"
|
||||||
|
end
|
||||||
|
end.join("&")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def transmit(uri, req, payload)
|
||||||
|
setup_credentials(req)
|
||||||
|
|
||||||
|
net = net_http_class.new(uri.host, uri.port)
|
||||||
|
net.use_ssl = uri.is_a?(URI::HTTPS)
|
||||||
|
if @verify_ssl == false
|
||||||
|
net.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
|
elsif @verify_ssl.is_a? Integer
|
||||||
|
net.verify_mode = @verify_ssl
|
||||||
|
end
|
||||||
|
net.cert = @ssl_client_cert if @ssl_client_cert
|
||||||
|
net.key = @ssl_client_key if @ssl_client_key
|
||||||
|
net.ca_file = @ssl_ca_file if @ssl_ca_file
|
||||||
|
net.read_timeout = @timeout if @timeout
|
||||||
|
net.open_timeout = @open_timeout if @open_timeout
|
||||||
|
|
||||||
|
display_log request_log
|
||||||
|
|
||||||
|
net.start do |http|
|
||||||
|
res = http.request(req, payload) { |http_response| fetch_body(http_response) }
|
||||||
|
result = process_result(res)
|
||||||
|
display_log response_log(res)
|
||||||
|
|
||||||
|
if result.kind_of?(String) or @method == :head
|
||||||
|
Response.new(result, res)
|
||||||
|
elsif @raw_response
|
||||||
|
RawResponse.new(@tf, res)
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue EOFError
|
||||||
|
raise RestClient::ServerBrokeConnection
|
||||||
|
rescue Timeout::Error
|
||||||
|
raise RestClient::RequestTimeout
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup_credentials(req)
|
||||||
|
req.basic_auth(user, password) if user
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_body(http_response)
|
||||||
|
if @raw_response
|
||||||
|
# Taken from Chef, which as in turn...
|
||||||
|
# Stolen from http://www.ruby-forum.com/topic/166423
|
||||||
|
# Kudos to _why!
|
||||||
|
@tf = Tempfile.new("rest-client")
|
||||||
|
size, total = 0, http_response.header['Content-Length'].to_i
|
||||||
|
http_response.read_body do |chunk|
|
||||||
|
@tf.write(chunk)
|
||||||
|
size += chunk.size
|
||||||
|
if size == 0
|
||||||
|
display_log("#{@method} #{@url} done (0 length file)")
|
||||||
|
elsif total == 0
|
||||||
|
display_log("#{@method} #{@url} (zero content length)")
|
||||||
|
else
|
||||||
|
display_log("#{@method} #{@url} %d%% done (%d of %d)" % [(size * 100) / total, size, total])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@tf.close
|
||||||
|
@tf
|
||||||
|
else
|
||||||
|
http_response.read_body
|
||||||
|
end
|
||||||
|
http_response
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_result(res)
|
||||||
|
if res.code =~ /\A2\d{2}\z/
|
||||||
|
# We don't decode raw requests
|
||||||
|
unless @raw_response
|
||||||
|
self.class.decode res['content-encoding'], res.body if res.body
|
||||||
|
end
|
||||||
|
elsif %w(301 302 303).include? res.code
|
||||||
|
url = res.header['Location']
|
||||||
|
|
||||||
|
if url !~ /^http/
|
||||||
|
uri = URI.parse(@url)
|
||||||
|
uri.path = "/#{url}".squeeze('/')
|
||||||
|
url = uri.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
raise Redirect, url
|
||||||
|
elsif res.code == "304"
|
||||||
|
raise NotModified, res
|
||||||
|
elsif res.code == "401"
|
||||||
|
raise Unauthorized, res
|
||||||
|
elsif res.code == "404"
|
||||||
|
raise ResourceNotFound, res
|
||||||
|
else
|
||||||
|
raise RequestFailed, res
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.decode(content_encoding, body)
|
||||||
|
if content_encoding == 'gzip' and not body.empty?
|
||||||
|
Zlib::GzipReader.new(StringIO.new(body)).read
|
||||||
|
elsif content_encoding == 'deflate'
|
||||||
|
Zlib::Inflate.new.inflate(body)
|
||||||
|
else
|
||||||
|
body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def request_log
|
||||||
|
out = []
|
||||||
|
out << "RestClient.#{method} #{url.inspect}"
|
||||||
|
out << (payload.size > 100 ? "(#{payload.size} byte payload)".inspect : payload.inspect) if payload
|
||||||
|
out << headers.inspect.gsub(/^\{/, '').gsub(/\}$/, '') unless headers.empty?
|
||||||
|
out.join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
|
def response_log(res)
|
||||||
|
size = @raw_response ? File.size(@tf.path) : (res.body.nil? ? 0 : res.body.size)
|
||||||
|
"# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes"
|
||||||
|
end
|
||||||
|
|
||||||
|
def display_log(msg)
|
||||||
|
return unless log_to = RestClient.log
|
||||||
|
|
||||||
|
if log_to == 'stdout'
|
||||||
|
STDOUT.puts msg
|
||||||
|
elsif log_to == 'stderr'
|
||||||
|
STDERR.puts msg
|
||||||
|
else
|
||||||
|
File.open(log_to, 'a') { |f| f.puts msg }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_headers
|
||||||
|
{ :accept => '*/*; q=0.5, application/xml', :accept_encoding => 'gzip, deflate' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -16,6 +16,10 @@ module RestClient
|
||||||
#
|
#
|
||||||
# RestClient::Resource.new('http://slow', :timeout => 10)
|
# RestClient::Resource.new('http://slow', :timeout => 10)
|
||||||
#
|
#
|
||||||
|
# With an open timeout (seconds):
|
||||||
|
#
|
||||||
|
# RestClient::Resource.new('http://behindfirewall', :open_timeout => 10)
|
||||||
|
#
|
||||||
# You can also use resources to share common headers. For headers keys,
|
# You can also use resources to share common headers. For headers keys,
|
||||||
# symbols are converted to strings. Example:
|
# symbols are converted to strings. Example:
|
||||||
#
|
#
|
||||||
|
@ -89,9 +93,10 @@ module RestClient
|
||||||
|
|
||||||
def timeout
|
def timeout
|
||||||
options[:timeout]
|
options[:timeout]
|
||||||
:user => user,
|
end
|
||||||
:password => password,
|
|
||||||
:headers => headers, &b)
|
def open_timeout
|
||||||
|
options[:open_timeout]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Construct a subresource, preserving authentication.
|
# Construct a subresource, preserving authentication.
|
20
lib/restclient/response.rb
Normal file
20
lib/restclient/response.rb
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
require File.dirname(__FILE__) + '/mixin/response'
|
||||||
|
|
||||||
|
module RestClient
|
||||||
|
# The response from RestClient looks like a string, but is actually one of
|
||||||
|
# these. 99% of the time you're making a rest call all you care about is
|
||||||
|
# the body, but on the occassion you want to fetch the headers you can:
|
||||||
|
#
|
||||||
|
# RestClient.get('http://example.com').headers[:content_type]
|
||||||
|
#
|
||||||
|
class Response < String
|
||||||
|
|
||||||
|
include RestClient::Mixin::Response
|
||||||
|
|
||||||
|
def initialize(string, net_http_res)
|
||||||
|
@net_http_res = net_http_res
|
||||||
|
super(string || "")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,18 +1,70 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "rest-client"
|
s.name = %q{rest-client}
|
||||||
s.version = "0.8.2"
|
s.version = "1.1.0"
|
||||||
s.summary = "Simple REST client for Ruby, inspired by microframework syntax for specifying actions."
|
|
||||||
s.description = "A simple REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete."
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||||
s.author = "Adam Wiggins"
|
s.authors = ["Adam Wiggins"]
|
||||||
s.email = "adam@heroku.com"
|
s.date = %q{2009-08-12}
|
||||||
s.rubyforge_project = "rest-client"
|
s.default_executable = %q{restclient}
|
||||||
s.homepage = "http://rest-client.heroku.com/"
|
s.description = %q{A simple REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete.}
|
||||||
s.has_rdoc = true
|
s.email = %q{adam@heroku.com}
|
||||||
s.platform = Gem::Platform::RUBY
|
s.executables = ["restclient"]
|
||||||
s.files = %w(Rakefile README.rdoc rest-client.gemspec
|
s.extra_rdoc_files = [
|
||||||
lib/request_errors.rb lib/resource.rb lib/rest_client.rb
|
"README.rdoc"
|
||||||
spec/base.rb spec/request_errors_spec.rb spec/resource_spec.rb spec/rest_client_spec.rb
|
]
|
||||||
bin/restclient)
|
s.files = [
|
||||||
s.executables = ['restclient']
|
"README.rdoc",
|
||||||
s.require_path = "lib"
|
"Rakefile",
|
||||||
|
"VERSION",
|
||||||
|
"bin/restclient",
|
||||||
|
"lib/rest_client.rb",
|
||||||
|
"lib/restclient.rb",
|
||||||
|
"lib/restclient/exceptions.rb",
|
||||||
|
"lib/restclient/mixin/response.rb",
|
||||||
|
"lib/restclient/net_http_ext.rb",
|
||||||
|
"lib/restclient/payload.rb",
|
||||||
|
"lib/restclient/raw_response.rb",
|
||||||
|
"lib/restclient/request.rb",
|
||||||
|
"lib/restclient/resource.rb",
|
||||||
|
"lib/restclient/response.rb",
|
||||||
|
"spec/base.rb",
|
||||||
|
"spec/exceptions_spec.rb",
|
||||||
|
"spec/master_shake.jpg",
|
||||||
|
"spec/mixin/response_spec.rb",
|
||||||
|
"spec/payload_spec.rb",
|
||||||
|
"spec/raw_response_spec.rb",
|
||||||
|
"spec/request_spec.rb",
|
||||||
|
"spec/resource_spec.rb",
|
||||||
|
"spec/response_spec.rb",
|
||||||
|
"spec/restclient_spec.rb"
|
||||||
|
]
|
||||||
|
s.homepage = %q{http://rest-client.heroku.com/}
|
||||||
|
s.rdoc_options = ["--charset=UTF-8"]
|
||||||
|
s.require_paths = ["lib"]
|
||||||
|
s.rubyforge_project = %q{rest-client}
|
||||||
|
s.rubygems_version = %q{1.3.5}
|
||||||
|
s.summary = %q{Simple REST client for Ruby, inspired by microframework syntax for specifying actions.}
|
||||||
|
s.test_files = [
|
||||||
|
"spec/base.rb",
|
||||||
|
"spec/exceptions_spec.rb",
|
||||||
|
"spec/mixin/response_spec.rb",
|
||||||
|
"spec/payload_spec.rb",
|
||||||
|
"spec/raw_response_spec.rb",
|
||||||
|
"spec/request_spec.rb",
|
||||||
|
"spec/resource_spec.rb",
|
||||||
|
"spec/response_spec.rb",
|
||||||
|
"spec/restclient_spec.rb"
|
||||||
|
]
|
||||||
|
|
||||||
|
if s.respond_to? :specification_version then
|
||||||
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
||||||
|
s.specification_version = 3
|
||||||
|
|
||||||
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
||||||
|
else
|
||||||
|
end
|
||||||
|
else
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
require 'spec'
|
require 'spec'
|
||||||
|
|
||||||
require File.dirname(__FILE__) + '/../lib/rest_client'
|
require File.dirname(__FILE__) + '/../lib/restclient'
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,10 @@ describe RestClient::Exception do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe RestClient::RequestFailed do
|
describe RestClient::RequestFailed do
|
||||||
|
before do
|
||||||
|
@response = mock('HTTP Response', :code => '502')
|
||||||
|
end
|
||||||
|
|
||||||
it "stores the http response on the exception" do
|
it "stores the http response on the exception" do
|
||||||
begin
|
begin
|
||||||
raise RestClient::RequestFailed, :response
|
raise RestClient::RequestFailed, :response
|
||||||
|
@ -21,11 +25,18 @@ describe RestClient::RequestFailed do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "http_code convenience method for fetching the code as an integer" do
|
it "http_code convenience method for fetching the code as an integer" do
|
||||||
RestClient::RequestFailed.new(mock('res', :code => '502')).http_code.should == 502
|
RestClient::RequestFailed.new(@response).http_code.should == 502
|
||||||
|
end
|
||||||
|
|
||||||
|
it "http_body convenience method for fetching the body (decoding when necessary)" do
|
||||||
|
@response.stub!(:[]).with('content-encoding').and_return('gzip')
|
||||||
|
@response.stub!(:body).and_return('compressed body')
|
||||||
|
RestClient::Request.should_receive(:decode).with('gzip', 'compressed body').and_return('plain body')
|
||||||
|
RestClient::RequestFailed.new(@response).http_body.should == 'plain body'
|
||||||
end
|
end
|
||||||
|
|
||||||
it "shows the status code in the message" do
|
it "shows the status code in the message" do
|
||||||
RestClient::RequestFailed.new(mock('res', :code => '502')).to_s.should match(/502/)
|
RestClient::RequestFailed.new(@response).to_s.should match(/502/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
46
spec/mixin/response_spec.rb
Normal file
46
spec/mixin/response_spec.rb
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
require File.dirname(__FILE__) + '/../base'
|
||||||
|
|
||||||
|
class MockResponse
|
||||||
|
include RestClient::Mixin::Response
|
||||||
|
|
||||||
|
def initialize(body, res)
|
||||||
|
@net_http_res = res
|
||||||
|
@body = @body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe RestClient::Mixin::Response do
|
||||||
|
before do
|
||||||
|
@net_http_res = mock('net http response')
|
||||||
|
@response = MockResponse.new('abc', @net_http_res)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fetches the numeric response code" do
|
||||||
|
@net_http_res.should_receive(:code).and_return('200')
|
||||||
|
@response.code.should == 200
|
||||||
|
end
|
||||||
|
|
||||||
|
it "beautifies the headers by turning the keys to symbols" do
|
||||||
|
h = RestClient::Response.beautify_headers('content-type' => [ 'x' ])
|
||||||
|
h.keys.first.should == :content_type
|
||||||
|
end
|
||||||
|
|
||||||
|
it "beautifies the headers by turning the values to strings instead of one-element arrays" do
|
||||||
|
h = RestClient::Response.beautify_headers('x' => [ 'text/html' ] )
|
||||||
|
h.values.first.should == 'text/html'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fetches the headers" do
|
||||||
|
@net_http_res.should_receive(:to_hash).and_return('content-type' => [ 'text/html' ])
|
||||||
|
@response.headers.should == { :content_type => 'text/html' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "extracts cookies from response headers" do
|
||||||
|
@net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/'])
|
||||||
|
@response.cookies.should == { 'session_id' => '1' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can access the net http result directly" do
|
||||||
|
@response.net_http_res.should == @net_http_res
|
||||||
|
end
|
||||||
|
end
|
|
@ -36,7 +36,7 @@ EOS
|
||||||
m = RestClient::Payload::Multipart.new({:foo => f})
|
m = RestClient::Payload::Multipart.new({:foo => f})
|
||||||
m.to_s.should == <<-EOS
|
m.to_s.should == <<-EOS
|
||||||
--#{m.boundary}\r
|
--#{m.boundary}\r
|
||||||
Content-Disposition: multipart/form-data; name="foo"; filename="./spec/master_shake.jpg"\r
|
Content-Disposition: multipart/form-data; name="foo"; filename="master_shake.jpg"\r
|
||||||
Content-Type: image/jpeg\r
|
Content-Type: image/jpeg\r
|
||||||
\r
|
\r
|
||||||
#{IO.read(f.path)}\r
|
#{IO.read(f.path)}\r
|
||||||
|
@ -63,5 +63,10 @@ EOS
|
||||||
it "should return data if no of the above" do
|
it "should return data if no of the above" do
|
||||||
RestClient::Payload.generate("data").should be_kind_of(RestClient::Payload::Base)
|
RestClient::Payload.generate("data").should be_kind_of(RestClient::Payload::Base)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should recognize nested multipart payloads" do
|
||||||
|
f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
|
||||||
|
RestClient::Payload.generate({"foo" => {"file" => f}}).should be_kind_of(RestClient::Payload::Multipart)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
17
spec/raw_response_spec.rb
Normal file
17
spec/raw_response_spec.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
require File.dirname(__FILE__) + '/base'
|
||||||
|
|
||||||
|
describe RestClient::RawResponse do
|
||||||
|
before do
|
||||||
|
@tf = mock("Tempfile", :read => "the answer is 42", :open => true)
|
||||||
|
@net_http_res = mock('net http response')
|
||||||
|
@response = RestClient::RawResponse.new(@tf, @net_http_res)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "behaves like string" do
|
||||||
|
@response.to_s.should == 'the answer is 42'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "exposes a Tempfile" do
|
||||||
|
@response.file.should == @tf
|
||||||
|
end
|
||||||
|
end
|
476
spec/request_spec.rb
Normal file
476
spec/request_spec.rb
Normal file
|
@ -0,0 +1,476 @@
|
||||||
|
require File.dirname(__FILE__) + '/base'
|
||||||
|
|
||||||
|
describe RestClient::Request do
|
||||||
|
before do
|
||||||
|
@request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload')
|
||||||
|
|
||||||
|
@uri = mock("uri")
|
||||||
|
@uri.stub!(:request_uri).and_return('/resource')
|
||||||
|
@uri.stub!(:host).and_return('some')
|
||||||
|
@uri.stub!(:port).and_return(80)
|
||||||
|
|
||||||
|
@net = mock("net::http base")
|
||||||
|
@http = mock("net::http connection")
|
||||||
|
Net::HTTP.stub!(:new).and_return(@net)
|
||||||
|
@net.stub!(:start).and_yield(@http)
|
||||||
|
@net.stub!(:use_ssl=)
|
||||||
|
@net.stub!(:verify_mode=)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "accept */* mimetype, preferring xml" do
|
||||||
|
@request.default_headers[:accept].should == '*/*; q=0.5, application/xml'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "decodes an uncompressed result body by passing it straight through" do
|
||||||
|
RestClient::Request.decode(nil, 'xyz').should == 'xyz'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "decodes a gzip body" do
|
||||||
|
RestClient::Request.decode('gzip', "\037\213\b\b\006'\252H\000\003t\000\313T\317UH\257\312,HM\341\002\000G\242(\r\v\000\000\000").should == "i'm gziped\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "ingores gzip for empty bodies" do
|
||||||
|
RestClient::Request.decode('gzip', '').should be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "decodes a deflated body" do
|
||||||
|
RestClient::Request.decode('deflate', "x\234+\316\317MUHIM\313I,IMQ(I\255(\001\000A\223\006\363").should == "some deflated text"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "processes a successful result" do
|
||||||
|
res = mock("result")
|
||||||
|
res.stub!(:code).and_return("200")
|
||||||
|
res.stub!(:body).and_return('body')
|
||||||
|
res.stub!(:[]).with('content-encoding').and_return(nil)
|
||||||
|
@request.process_result(res).should == 'body'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't classify successful requests as failed" do
|
||||||
|
203.upto(206) do |code|
|
||||||
|
res = mock("result")
|
||||||
|
res.stub!(:code).and_return(code.to_s)
|
||||||
|
res.stub!(:body).and_return("")
|
||||||
|
res.stub!(:[]).with('content-encoding').and_return(nil)
|
||||||
|
@request.process_result(res).should be_empty
|
||||||
|
end
|
||||||
|
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 "extracts the username and password when parsing http://user:password@example.com/" do
|
||||||
|
URI.stub!(:parse).and_return(mock('uri', :user => 'joe', :password => 'pass1'))
|
||||||
|
@request.parse_url_with_auth('http://joe:pass1@example.com/resource')
|
||||||
|
@request.user.should == 'joe'
|
||||||
|
@request.password.should == 'pass1'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't overwrite user and password (which may have already been set by the Resource constructor) if there is no user/password in the url" do
|
||||||
|
URI.stub!(:parse).and_return(mock('uri', :user => nil, :password => nil))
|
||||||
|
@request = RestClient::Request.new(:method => 'get', :url => 'example.com', :user => 'beth', :password => 'pass2')
|
||||||
|
@request.parse_url_with_auth('http://example.com/resource')
|
||||||
|
@request.user.should == 'beth'
|
||||||
|
@request.password.should == 'pass2'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "correctly formats cookies provided to the constructor" do
|
||||||
|
URI.stub!(:parse).and_return(mock('uri', :user => nil, :password => nil))
|
||||||
|
@request = RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {:session_id => '1' })
|
||||||
|
@request.should_receive(:default_headers).and_return({'foo' => 'bar'})
|
||||||
|
headers = @request.make_headers({}).should == { 'Foo' => 'bar', 'Cookie' => 'session_id=1'}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "determines the Net::HTTP class to instantiate by the method name" do
|
||||||
|
@request.net_http_request_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 "converts header values to strings" do
|
||||||
|
@request.make_headers('A' => 1)['A'].should == '1'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "executes by constructing the Net::HTTP object, headers, and payload and calling transmit" do
|
||||||
|
@request.should_receive(:parse_url_with_auth).with('http://some/resource').and_return(@uri)
|
||||||
|
klass = mock("net:http class")
|
||||||
|
@request.should_receive(:net_http_request_class).with(:put).and_return(klass)
|
||||||
|
klass.should_receive(:new).and_return('result')
|
||||||
|
@request.should_receive(:transmit).with(@uri, 'result', 'payload')
|
||||||
|
@request.execute_inner
|
||||||
|
end
|
||||||
|
|
||||||
|
it "transmits the request with Net::HTTP" do
|
||||||
|
@http.should_receive(:request).with('req', 'payload')
|
||||||
|
@request.should_receive(:process_result)
|
||||||
|
@request.should_receive(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "uses SSL when the URI refers to a https address" do
|
||||||
|
@uri.stub!(:is_a?).with(URI::HTTPS).and_return(true)
|
||||||
|
@net.should_receive(:use_ssl=).with(true)
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sends nil payloads" do
|
||||||
|
@http.should_receive(:request).with('req', nil)
|
||||||
|
@request.should_receive(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "passes non-hash payloads straight through" do
|
||||||
|
@request.process_payload("x").should == "x"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "converts a hash payload to urlencoded data" do
|
||||||
|
@request.process_payload(:a => 'b c+d').should == "a=b%20c%2Bd"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "accepts nested hashes in payload" do
|
||||||
|
payload = @request.process_payload(:user => { :name => 'joe', :location => { :country => 'USA', :state => 'CA' }})
|
||||||
|
payload.should include('user[name]=joe')
|
||||||
|
payload.should include('user[location][country]=USA')
|
||||||
|
payload.should include('user[location][state]=CA')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "set urlencoded content_type header on hash payloads" do
|
||||||
|
@request.process_payload(:a => 1)
|
||||||
|
@request.headers[:content_type].should == 'application/x-www-form-urlencoded'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets up the credentials prior to the request" do
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
|
||||||
|
@request.stub!(:user).and_return('joe')
|
||||||
|
@request.stub!(:password).and_return('mypass')
|
||||||
|
@request.should_receive(:setup_credentials).with('req')
|
||||||
|
|
||||||
|
@request.transmit(@uri, 'req', nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not attempt to send any credentials if user is nil" do
|
||||||
|
@request.stub!(:user).and_return(nil)
|
||||||
|
req = mock("request")
|
||||||
|
req.should_not_receive(:basic_auth)
|
||||||
|
@request.setup_credentials(req)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "setup credentials when there's a user" do
|
||||||
|
@request.stub!(:user).and_return('joe')
|
||||||
|
@request.stub!(:password).and_return('mypass')
|
||||||
|
req = mock("request")
|
||||||
|
req.should_receive(:basic_auth).with('joe', 'mypass')
|
||||||
|
@request.setup_credentials(req)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "catches EOFError and shows the more informative ServerBrokeConnection" do
|
||||||
|
@http.stub!(:request).and_raise(EOFError)
|
||||||
|
lambda { @request.transmit(@uri, 'req', nil) }.should raise_error(RestClient::ServerBrokeConnection)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "execute calls execute_inner" do
|
||||||
|
@request.should_receive(:execute_inner)
|
||||||
|
@request.execute
|
||||||
|
end
|
||||||
|
|
||||||
|
it "class method execute wraps constructor" do
|
||||||
|
req = mock("rest request")
|
||||||
|
RestClient::Request.should_receive(:new).with(1 => 2).and_return(req)
|
||||||
|
req.should_receive(:execute)
|
||||||
|
RestClient::Request.execute(1 => 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises a Redirect with the new location when the response is in the 30x range" do
|
||||||
|
res = mock('response', :code => '301', :header => { 'Location' => 'http://new/resource' })
|
||||||
|
lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://new/resource'}
|
||||||
|
end
|
||||||
|
|
||||||
|
it "handles redirects with relative paths" do
|
||||||
|
res = mock('response', :code => '301', :header => { 'Location' => 'index' })
|
||||||
|
lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://some/index' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "handles redirects with absolute paths" do
|
||||||
|
@request.instance_variable_set('@url', 'http://some/place/else')
|
||||||
|
res = mock('response', :code => '301', :header => { 'Location' => '/index' })
|
||||||
|
lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://some/index' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "uses GET and clears payload when following 30x redirects" do
|
||||||
|
url = "http://example.com/redirected"
|
||||||
|
|
||||||
|
@request.should_receive(:execute_inner).once.ordered.and_raise(RestClient::Redirect.new(url))
|
||||||
|
|
||||||
|
@request.should_receive(:execute_inner).once.ordered do
|
||||||
|
@request.url.should == url
|
||||||
|
@request.method.should == :get
|
||||||
|
@request.payload.should be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
@request.execute
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises Unauthorized when the response is 401" do
|
||||||
|
res = mock('response', :code => '401')
|
||||||
|
lambda { @request.process_result(res) }.should raise_error(RestClient::Unauthorized)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises ResourceNotFound when the response is 404" do
|
||||||
|
res = mock('response', :code => '404')
|
||||||
|
lambda { @request.process_result(res) }.should raise_error(RestClient::ResourceNotFound)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises RequestFailed otherwise" do
|
||||||
|
res = mock('response', :code => '500')
|
||||||
|
lambda { @request.process_result(res) }.should raise_error(RestClient::RequestFailed)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a proxy class if a proxy url is given" do
|
||||||
|
RestClient.stub!(:proxy).and_return("http://example.com/")
|
||||||
|
@request.net_http_class.should include(Net::HTTP::ProxyDelta)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a non-proxy class if a proxy url is not given" do
|
||||||
|
@request.net_http_class.should_not include(Net::HTTP::ProxyDelta)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "logs a get request" do
|
||||||
|
RestClient::Request.new(:method => :get, :url => 'http://url').request_log.should ==
|
||||||
|
'RestClient.get "http://url"'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "logs a post request with a small payload" do
|
||||||
|
RestClient::Request.new(:method => :post, :url => 'http://url', :payload => 'foo').request_log.should ==
|
||||||
|
'RestClient.post "http://url", "foo"'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "logs a post request with a large payload" do
|
||||||
|
RestClient::Request.new(:method => :post, :url => 'http://url', :payload => ('x' * 1000)).request_log.should ==
|
||||||
|
'RestClient.post "http://url", "(1000 byte payload)"'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "logs input headers as a hash" do
|
||||||
|
RestClient::Request.new(:method => :get, :url => 'http://url', :headers => { :accept => 'text/plain' }).request_log.should ==
|
||||||
|
'RestClient.get "http://url", :accept=>"text/plain"'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "logs a response including the status code, content type, and result body size in bytes" do
|
||||||
|
res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
|
||||||
|
res.stub!(:[]).with('Content-type').and_return('text/html')
|
||||||
|
@request.response_log(res).should == "# => 200 OK | text/html 4 bytes"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "logs a response with a nil Content-type" do
|
||||||
|
res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
|
||||||
|
res.stub!(:[]).with('Content-type').and_return(nil)
|
||||||
|
@request.response_log(res).should == "# => 200 OK | 4 bytes"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "logs a response with a nil body" do
|
||||||
|
res = mock('result', :code => '200', :class => Net::HTTPOK, :body => nil)
|
||||||
|
res.stub!(:[]).with('Content-type').and_return('text/html; charset=utf-8')
|
||||||
|
@request.response_log(res).should == "# => 200 OK | text/html 0 bytes"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "strips the charset from the response content type" do
|
||||||
|
res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
|
||||||
|
res.stub!(:[]).with('Content-type').and_return('text/html; charset=utf-8')
|
||||||
|
@request.response_log(res).should == "# => 200 OK | text/html 4 bytes"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "displays the log to stdout" do
|
||||||
|
RestClient.stub!(:log).and_return('stdout')
|
||||||
|
STDOUT.should_receive(:puts).with('xyz')
|
||||||
|
@request.display_log('xyz')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "displays the log to stderr" do
|
||||||
|
RestClient.stub!(:log).and_return('stderr')
|
||||||
|
STDERR.should_receive(:puts).with('xyz')
|
||||||
|
@request.display_log('xyz')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "append the log to the requested filename" do
|
||||||
|
RestClient.stub!(:log).and_return('/tmp/restclient.log')
|
||||||
|
f = mock('file handle')
|
||||||
|
File.should_receive(:open).with('/tmp/restclient.log', 'a').and_yield(f)
|
||||||
|
f.should_receive(:puts).with('xyz')
|
||||||
|
@request.display_log('xyz')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "set read_timeout" do
|
||||||
|
@request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :timeout => 123)
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
|
||||||
|
@net.should_receive(:read_timeout=).with(123)
|
||||||
|
|
||||||
|
@request.transmit(@uri, 'req', nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "set open_timeout" do
|
||||||
|
@request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :open_timeout => 123)
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
|
||||||
|
@net.should_receive(:open_timeout=).with(123)
|
||||||
|
|
||||||
|
@request.transmit(@uri, 'req', nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should default to not verifying ssl certificates" do
|
||||||
|
@request.verify_ssl.should == false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should set net.verify_mode to OpenSSL::SSL::VERIFY_NONE if verify_ssl is false" do
|
||||||
|
@net.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not set net.verify_mode to OpenSSL::SSL::VERIFY_NONE if verify_ssl is true" do
|
||||||
|
@request = RestClient::Request.new(:method => :put, :url => 'https://some/resource', :payload => 'payload', :verify_ssl => true)
|
||||||
|
@net.should_not_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should set net.verify_mode to the passed value if verify_ssl is an OpenSSL constant" do
|
||||||
|
mode = OpenSSL::SSL::VERIFY_PEER |
|
||||||
|
OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
||||||
|
@request = RestClient::Request.new( :method => :put,
|
||||||
|
:url => 'https://some/resource',
|
||||||
|
:payload => 'payload',
|
||||||
|
:verify_ssl => mode )
|
||||||
|
@net.should_receive(:verify_mode=).with(mode)
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should default to not having an ssl_client_cert" do
|
||||||
|
@request.ssl_client_cert.should be(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should set the ssl_client_cert if provided" do
|
||||||
|
@request = RestClient::Request.new(
|
||||||
|
:method => :put,
|
||||||
|
:url => 'https://some/resource',
|
||||||
|
:payload => 'payload',
|
||||||
|
:ssl_client_cert => "whatsupdoc!"
|
||||||
|
)
|
||||||
|
@net.should_receive(:cert=).with("whatsupdoc!")
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not set the ssl_client_cert if it is not provided" do
|
||||||
|
@request = RestClient::Request.new(
|
||||||
|
:method => :put,
|
||||||
|
:url => 'https://some/resource',
|
||||||
|
:payload => 'payload'
|
||||||
|
)
|
||||||
|
@net.should_not_receive(:cert=).with("whatsupdoc!")
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should default to not having an ssl_client_key" do
|
||||||
|
@request.ssl_client_key.should be(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should set the ssl_client_key if provided" do
|
||||||
|
@request = RestClient::Request.new(
|
||||||
|
:method => :put,
|
||||||
|
:url => 'https://some/resource',
|
||||||
|
:payload => 'payload',
|
||||||
|
:ssl_client_key => "whatsupdoc!"
|
||||||
|
)
|
||||||
|
@net.should_receive(:key=).with("whatsupdoc!")
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not set the ssl_client_key if it is not provided" do
|
||||||
|
@request = RestClient::Request.new(
|
||||||
|
:method => :put,
|
||||||
|
:url => 'https://some/resource',
|
||||||
|
:payload => 'payload'
|
||||||
|
)
|
||||||
|
@net.should_not_receive(:key=).with("whatsupdoc!")
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should default to not having an ssl_ca_file" do
|
||||||
|
@request.ssl_ca_file.should be(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should set the ssl_ca_file if provided" do
|
||||||
|
@request = RestClient::Request.new(
|
||||||
|
:method => :put,
|
||||||
|
:url => 'https://some/resource',
|
||||||
|
:payload => 'payload',
|
||||||
|
:ssl_ca_file => "Certificate Authority File"
|
||||||
|
)
|
||||||
|
@net.should_receive(:ca_file=).with("Certificate Authority File")
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not set the ssl_ca_file if it is not provided" do
|
||||||
|
@request = RestClient::Request.new(
|
||||||
|
:method => :put,
|
||||||
|
:url => 'https://some/resource',
|
||||||
|
:payload => 'payload'
|
||||||
|
)
|
||||||
|
@net.should_not_receive(:ca_file=).with("Certificate Authority File")
|
||||||
|
@http.stub!(:request)
|
||||||
|
@request.stub!(:process_result)
|
||||||
|
@request.stub!(:response_log)
|
||||||
|
@request.transmit(@uri, 'req', 'payload')
|
||||||
|
end
|
||||||
|
end
|
16
spec/response_spec.rb
Normal file
16
spec/response_spec.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
require File.dirname(__FILE__) + '/base'
|
||||||
|
|
||||||
|
describe RestClient::Response do
|
||||||
|
before do
|
||||||
|
@net_http_res = mock('net http response')
|
||||||
|
@response = RestClient::Response.new('abc', @net_http_res)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "behaves like string" do
|
||||||
|
@response.should == 'abc'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "accepts nil strings and sets it to empty for the case of HEAD" do
|
||||||
|
RestClient::Response.new(nil, @net_http_res).should == ""
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,383 +0,0 @@
|
||||||
require File.dirname(__FILE__) + '/base'
|
|
||||||
|
|
||||||
def generate_payload(v)
|
|
||||||
RestClient::Payload::Base.new(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe RestClient do
|
|
||||||
context "public API" do
|
|
||||||
it "GET" do
|
|
||||||
RestClient::Request.should_receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {})
|
|
||||||
RestClient.get('http://some/resource')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "POST" do
|
|
||||||
RestClient::Request.should_receive(:execute).with(:method => :post, :url => 'http://some/resource', :payload => 'payload', :headers => {})
|
|
||||||
RestClient.post('http://some/resource', 'payload')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "PUT" do
|
|
||||||
RestClient::Request.should_receive(:execute).with(:method => :put, :url => 'http://some/resource', :payload => 'payload', :headers => {})
|
|
||||||
RestClient.put('http://some/resource', 'payload')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "DELETE" do
|
|
||||||
RestClient::Request.should_receive(:execute).with(:method => :delete, :url => 'http://some/resource', :headers => {})
|
|
||||||
RestClient.delete('http://some/resource')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "logging" do
|
|
||||||
after do
|
|
||||||
RestClient.log = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it "gets the log source from the RESTCLIENT_LOG environment variable" do
|
|
||||||
ENV.stub!(:[]).with('RESTCLIENT_LOG').and_return('from env')
|
|
||||||
RestClient.log = 'from class method'
|
|
||||||
RestClient.log.should == 'from env'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "sets a destination for log output, used if no environment variable is set" do
|
|
||||||
ENV.stub!(:[]).with('RESTCLIENT_LOG').and_return(nil)
|
|
||||||
RestClient.log = 'from class method'
|
|
||||||
RestClient.log.should == 'from class method'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns nil (no logging) if neither are set (default)" do
|
|
||||||
ENV.stub!(:[]).with('RESTCLIENT_LOG').and_return(nil)
|
|
||||||
RestClient.log.should == nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context RestClient::Request do
|
|
||||||
before do
|
|
||||||
@request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload')
|
|
||||||
|
|
||||||
@uri = mock("uri")
|
|
||||||
@uri.stub!(:request_uri).and_return('/resource')
|
|
||||||
@uri.stub!(:host).and_return('some')
|
|
||||||
@uri.stub!(:port).and_return(80)
|
|
||||||
|
|
||||||
@net = mock("net::http base")
|
|
||||||
@http = mock("net::http connection")
|
|
||||||
Net::HTTP.stub!(:new).and_return(@net)
|
|
||||||
@net.stub!(:start).and_yield(@http)
|
|
||||||
@net.stub!(:use_ssl=)
|
|
||||||
@net.stub!(:verify_mode=)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "requests xml mimetype" do
|
|
||||||
@request.default_headers[:accept].should == 'application/xml'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "decodes an uncompressed result body by passing it straight through" do
|
|
||||||
@request.decode(nil, 'xyz').should == 'xyz'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "decodes a gzip body" do
|
|
||||||
@request.decode('gzip', "\037\213\b\b\006'\252H\000\003t\000\313T\317UH\257\312,HM\341\002\000G\242(\r\v\000\000\000").should == "i'm gziped\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "decodes a deflated body" do
|
|
||||||
@request.decode('deflate', "x\234+\316\317MUHIM\313I,IMQ(I\255(\001\000A\223\006\363").should == "some deflated text"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "processes a successful result" do
|
|
||||||
res = mock("result")
|
|
||||||
res.stub!(:code).and_return("200")
|
|
||||||
res.stub!(:body).and_return('body')
|
|
||||||
res.stub!(:[]).with('content-encoding').and_return(nil)
|
|
||||||
@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 "extracts the username and password when parsing http://user:password@example.com/" do
|
|
||||||
URI.stub!(:parse).and_return(mock('uri', :user => 'joe', :password => 'pass1'))
|
|
||||||
@request.parse_url_with_auth('http://joe:pass1@example.com/resource')
|
|
||||||
@request.user.should == 'joe'
|
|
||||||
@request.password.should == 'pass1'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't overwrite user and password (which may have already been set by the Resource constructor) if there is no user/password in the url" do
|
|
||||||
URI.stub!(:parse).and_return(mock('uri', :user => nil, :password => nil))
|
|
||||||
@request = RestClient::Request.new(:method => 'get', :url => 'example.com', :user => 'beth', :password => 'pass2')
|
|
||||||
@request.parse_url_with_auth('http://example.com/resource')
|
|
||||||
@request.user.should == 'beth'
|
|
||||||
@request.password.should == 'pass2'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "determines the Net::HTTP class to instantiate by the method name" do
|
|
||||||
@request.net_http_request_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 "converts header values to strings" do
|
|
||||||
@request.make_headers('A' => 1)['A'].should == '1'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "executes by constructing the Net::HTTP object, headers, and payload and calling transmit" do
|
|
||||||
@request.should_receive(:parse_url_with_auth).with('http://some/resource').and_return(@uri)
|
|
||||||
klass = mock("net:http class")
|
|
||||||
@request.should_receive(:net_http_request_class).with(:put).and_return(klass)
|
|
||||||
klass.should_receive(:new).and_return('result')
|
|
||||||
@request.should_receive(:transmit).with(@uri, 'result', be_kind_of(RestClient::Payload::Base))
|
|
||||||
@request.execute_inner
|
|
||||||
end
|
|
||||||
|
|
||||||
it "transmits the request with Net::HTTP" do
|
|
||||||
@http.should_receive(:request).with('req', be_kind_of(RestClient::Payload::Base))
|
|
||||||
@request.should_receive(:process_result)
|
|
||||||
@request.should_receive(:response_log)
|
|
||||||
@request.transmit(@uri, 'req', generate_payload('payload'))
|
|
||||||
end
|
|
||||||
|
|
||||||
it "uses SSL when the URI refers to a https address" do
|
|
||||||
@uri.stub!(:is_a?).with(URI::HTTPS).and_return(true)
|
|
||||||
@net.should_receive(:use_ssl=).with(true)
|
|
||||||
@http.stub!(:request)
|
|
||||||
@request.stub!(:process_result)
|
|
||||||
@request.stub!(:response_log)
|
|
||||||
@request.transmit(@uri, 'req', generate_payload('payload'))
|
|
||||||
end
|
|
||||||
|
|
||||||
it "sends nil payloads" do
|
|
||||||
@http.should_receive(:request).with('req', nil)
|
|
||||||
@request.should_receive(:process_result)
|
|
||||||
@request.stub!(:response_log)
|
|
||||||
@request.transmit(@uri, 'req', nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "passes non-hash payloads straight through" do
|
|
||||||
@request.process_payload("x").should == "x"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "converts a hash payload to urlencoded data" do
|
|
||||||
@request.process_payload(:a => 'b c+d').should == "a=b%20c%2Bd"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "accepts nested hashes in payload" do
|
|
||||||
payload = @request.process_payload(:user => { :name => 'joe', :location => { :country => 'USA', :state => 'CA' }})
|
|
||||||
payload.should include('user[name]=joe')
|
|
||||||
payload.should include('user[location][country]=USA')
|
|
||||||
payload.should include('user[location][state]=CA')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "set urlencoded content_type header on hash payloads" do
|
|
||||||
@request.process_payload(:a => 1)
|
|
||||||
@request.headers[:content_type].should == 'application/x-www-form-urlencoded'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "sets up the credentials prior to the request" do
|
|
||||||
@http.stub!(:request)
|
|
||||||
@request.stub!(:process_result)
|
|
||||||
@request.stub!(:response_log)
|
|
||||||
|
|
||||||
@request.stub!(:user).and_return('joe')
|
|
||||||
@request.stub!(:password).and_return('mypass')
|
|
||||||
@request.should_receive(:setup_credentials).with('req')
|
|
||||||
|
|
||||||
@request.transmit(@uri, 'req', generate_payload(''))
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not attempt to send any credentials if user is nil" do
|
|
||||||
@request.stub!(:user).and_return(nil)
|
|
||||||
req = mock("request")
|
|
||||||
req.should_not_receive(:basic_auth)
|
|
||||||
@request.setup_credentials(req)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "setup credentials when there's a user" do
|
|
||||||
@request.stub!(:user).and_return('joe')
|
|
||||||
@request.stub!(:password).and_return('mypass')
|
|
||||||
req = mock("request")
|
|
||||||
req.should_receive(:basic_auth).with('joe', 'mypass')
|
|
||||||
@request.setup_credentials(req)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "catches EOFError and shows the more informative ServerBrokeConnection" do
|
|
||||||
@http.stub!(:request).and_raise(EOFError)
|
|
||||||
lambda { @request.transmit(@uri, 'req', generate_payload('')) }.
|
|
||||||
should raise_error(RestClient::ServerBrokeConnection)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "execute calls execute_inner" do
|
|
||||||
@request.should_receive(:execute_inner)
|
|
||||||
@request.execute
|
|
||||||
end
|
|
||||||
|
|
||||||
it "class method execute wraps constructor" do
|
|
||||||
req = mock("rest request")
|
|
||||||
RestClient::Request.should_receive(:new).with(1 => 2).and_return(req)
|
|
||||||
req.should_receive(:execute)
|
|
||||||
RestClient::Request.execute(1 => 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises a Redirect with the new location when the response is in the 30x range" do
|
|
||||||
res = mock('response', :code => '301', :header => { 'Location' => 'http://new/resource' })
|
|
||||||
lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://new/resource'}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "handles redirects with relative paths" do
|
|
||||||
res = mock('response', :code => '301', :header => { 'Location' => 'index' })
|
|
||||||
lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://some/index' }
|
|
||||||
end
|
|
||||||
|
|
||||||
it "handles redirects with absolute paths" do
|
|
||||||
@request.instance_variable_set('@url', 'http://some/place/else')
|
|
||||||
res = mock('response', :code => '301', :header => { 'Location' => '/index' })
|
|
||||||
lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://some/index' }
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises Unauthorized when the response is 401" do
|
|
||||||
res = mock('response', :code => '401')
|
|
||||||
lambda { @request.process_result(res) }.should raise_error(RestClient::Unauthorized)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises ResourceNotFound when the response is 404" do
|
|
||||||
res = mock('response', :code => '404')
|
|
||||||
lambda { @request.process_result(res) }.should raise_error(RestClient::ResourceNotFound)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises RequestFailed otherwise" do
|
|
||||||
res = mock('response', :code => '500')
|
|
||||||
lambda { @request.process_result(res) }.should raise_error(RestClient::RequestFailed)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "creates a proxy class if a proxy url is given" do
|
|
||||||
RestClient.stub!(:proxy).and_return("http://example.com/")
|
|
||||||
@request.net_http_class.should include(Net::HTTP::ProxyDelta)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "creates a non-proxy class if a proxy url is not given" do
|
|
||||||
@request.net_http_class.should_not include(Net::HTTP::ProxyDelta)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "logs a get request" do
|
|
||||||
RestClient::Request.new(:method => :get, :url => 'http://url').request_log.should ==
|
|
||||||
'RestClient.get "http://url"'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "logs a post request with a small payload" do
|
|
||||||
RestClient::Request.new(:method => :post, :url => 'http://url', :payload => 'foo').request_log.should ==
|
|
||||||
'RestClient.post "http://url", "foo"'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "logs a post request with a large payload" do
|
|
||||||
RestClient::Request.new(:method => :post, :url => 'http://url', :payload => ('x' * 1000)).request_log.should ==
|
|
||||||
'RestClient.post "http://url", "(1000 byte payload)"'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "logs input headers as a hash" do
|
|
||||||
RestClient::Request.new(:method => :get, :url => 'http://url', :headers => { :accept => 'text/plain' }).request_log.should ==
|
|
||||||
'RestClient.get "http://url", :accept=>"text/plain"'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "logs a response including the status code, content type, and result body size in bytes" do
|
|
||||||
res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
|
|
||||||
res.stub!(:[]).with('Content-type').and_return('text/html')
|
|
||||||
@request.response_log(res).should == "# => 200 OK | text/html 4 bytes"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "logs a response with a nil Content-type" do
|
|
||||||
res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
|
|
||||||
res.stub!(:[]).with('Content-type').and_return(nil)
|
|
||||||
@request.response_log(res).should == "# => 200 OK | 4 bytes"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "strips the charset from the response content type" do
|
|
||||||
res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
|
|
||||||
res.stub!(:[]).with('Content-type').and_return('text/html; charset=utf-8')
|
|
||||||
@request.response_log(res).should == "# => 200 OK | text/html 4 bytes"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "displays the log to stdout" do
|
|
||||||
RestClient.stub!(:log).and_return('stdout')
|
|
||||||
STDOUT.should_receive(:puts).with('xyz')
|
|
||||||
@request.display_log('xyz')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "displays the log to stderr" do
|
|
||||||
RestClient.stub!(:log).and_return('stderr')
|
|
||||||
STDERR.should_receive(:puts).with('xyz')
|
|
||||||
@request.display_log('xyz')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "append the log to the requested filename" do
|
|
||||||
RestClient.stub!(:log).and_return('/tmp/restclient.log')
|
|
||||||
f = mock('file handle')
|
|
||||||
File.should_receive(:open).with('/tmp/restclient.log', 'a').and_yield(f)
|
|
||||||
f.should_receive(:puts).with('xyz')
|
|
||||||
@request.display_log('xyz')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "set read_timeout" do
|
|
||||||
@request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :timeout => 123)
|
|
||||||
@http.stub!(:request)
|
|
||||||
@request.stub!(:process_result)
|
|
||||||
@request.stub!(:response_log)
|
|
||||||
|
|
||||||
@http.should_receive(:read_timeout=).with(123)
|
|
||||||
|
|
||||||
@request.transmit(@uri, 'req', nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context RestClient::Response do
|
|
||||||
before do
|
|
||||||
@net_http_res = mock('net http response')
|
|
||||||
@response = RestClient::Response.new('abc', @net_http_res)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "behaves like string" do
|
|
||||||
@response.should == 'abc'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "fetches the numeric response code" do
|
|
||||||
@net_http_res.should_receive(:code).and_return('200')
|
|
||||||
@response.code.should == 200
|
|
||||||
end
|
|
||||||
|
|
||||||
it "beautifies the headers by turning the keys to symbols" do
|
|
||||||
h = RestClient::Response.beautify_headers('content-type' => [ 'x' ])
|
|
||||||
h.keys.first.should == :content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
it "beautifies the headers by turning the values to strings instead of one-element arrays" do
|
|
||||||
h = RestClient::Response.beautify_headers('x' => [ 'text/html' ] )
|
|
||||||
h.values.first.should == 'text/html'
|
|
||||||
end
|
|
||||||
|
|
||||||
it "fetches the headers" do
|
|
||||||
@net_http_res.should_receive(:to_hash).and_return('content-type' => [ 'text/html' ])
|
|
||||||
@response.headers.should == { :content_type => 'text/html' }
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can access the net http result directly" do
|
|
||||||
@response.net_http_res.should == @net_http_res
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
53
spec/restclient_spec.rb
Normal file
53
spec/restclient_spec.rb
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
require File.dirname(__FILE__) + '/base'
|
||||||
|
|
||||||
|
describe RestClient do
|
||||||
|
describe "API" do
|
||||||
|
it "GET" do
|
||||||
|
RestClient::Request.should_receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {})
|
||||||
|
RestClient.get('http://some/resource')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "POST" do
|
||||||
|
RestClient::Request.should_receive(:execute).with(:method => :post, :url => 'http://some/resource', :payload => 'payload', :headers => {})
|
||||||
|
RestClient.post('http://some/resource', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "PUT" do
|
||||||
|
RestClient::Request.should_receive(:execute).with(:method => :put, :url => 'http://some/resource', :payload => 'payload', :headers => {})
|
||||||
|
RestClient.put('http://some/resource', 'payload')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "DELETE" do
|
||||||
|
RestClient::Request.should_receive(:execute).with(:method => :delete, :url => 'http://some/resource', :headers => {})
|
||||||
|
RestClient.delete('http://some/resource')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "HEAD" do
|
||||||
|
RestClient::Request.should_receive(:execute).with(:method => :head, :url => 'http://some/resource', :headers => {})
|
||||||
|
RestClient.head('http://some/resource')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "logging" do
|
||||||
|
after do
|
||||||
|
RestClient.log = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "gets the log source from the RESTCLIENT_LOG environment variable" do
|
||||||
|
ENV.stub!(:[]).with('RESTCLIENT_LOG').and_return('from env')
|
||||||
|
RestClient.log = 'from class method'
|
||||||
|
RestClient.log.should == 'from env'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets a destination for log output, used if no environment variable is set" do
|
||||||
|
ENV.stub!(:[]).with('RESTCLIENT_LOG').and_return(nil)
|
||||||
|
RestClient.log = 'from class method'
|
||||||
|
RestClient.log.should == 'from class method'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns nil (no logging) if neither are set (default)" do
|
||||||
|
ENV.stub!(:[]).with('RESTCLIENT_LOG').and_return(nil)
|
||||||
|
RestClient.log.should == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Add a link
Reference in a new issue