1
0
Fork 0
mirror of https://github.com/rest-client/rest-client.git synced 2022-11-09 13:49:40 -05:00

Merge branch 'master' into rpanachi/1.2.0

This commit is contained in:
Julien Kirch 2010-01-31 10:30:19 +01:00
commit da0da219a5
22 changed files with 1002 additions and 623 deletions

View file

@ -1,6 +1,6 @@
= REST Client -- simple DSL for accessing REST resources
= REST Client -- simple DSL for accessing HTTP and REST resources
A simple REST client for Ruby, inspired by the Sinatra's microframework style
A simple HTTP and REST client for Ruby, inspired by the Sinatra's microframework style
of specifying actions: get, put, post, delete.
== Usage: Raw URL
@ -51,6 +51,51 @@ See RestClient::Resource module docs for details.
See RestClient::Resource docs for details.
== Exceptions
* for results code between 200 and 206 a RestClient::Response will be returned
* for results code between 301 and 303 the redirection will be automatically followed
* for other result codes a RestClient::Exception holding the Response will be raised, a specific exception class will be thrown for know error codes
RestClient.get 'http://example.com/resource'
➔ RestClient::ResourceNotFound: RestClient::ResourceNotFound
begin
RestClient.get 'http://example.com/resource'
rescue => e
e.response
end
➔ "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>404 Not Found</title>..."
== Result handling
A block can be passed to the RestClient method, this block will then be called with the Response.
Response.return! can be called to invoke the default response's behavior (return the Response for 200..206, raise an exception in other cases).
# Don't raise exceptions but return the response
RestClient.get('http://example.com/resource'){|response| response}
➔ "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>404 Not Found</title>..."
# Manage a specific error code
RestClient.get('http://my-rest-service.com/resource'){ |response|
case response.code
when 200
p "It worked !"
response
when 423
raise SomeCustomExceptionIfYouWant
else
response.return!
end
}
== Non-normalized URIs.
If you want to use non-normalized URIs, you can normalize them with the addressable gem (http://addressable.rubyforge.org/api/).
require 'addressable/uri'
RestClient.get(Addressable::URI.parse("http://www.詹姆斯.com/").normalize.to_str)
== Lower-level access
For cases not covered by the general API, you can use the RestClient::Resource class which provide a lower-level API, see the class' rdoc for more information.
@ -60,17 +105,17 @@ For cases not covered by the general API, you can use the RestClient::Resource c
The restclient shell command gives an IRB session with RestClient already loaded:
$ restclient
>> RestClient.get 'http://example.com'
RestClient.get 'http://example.com'
Specify a URL argument for get/post/put/delete on that resource:
$ restclient http://example.com
>> put '/resource', 'data'
put '/resource', 'data'
Add a user and password for authenticated resources:
$ restclient https://example.com user pass
>> delete '/private/resource'
delete '/private/resource'
Create ~/.restclient for named sessions:
@ -87,11 +132,78 @@ Then invoke:
$ restclient private_site
Use as a one-off, curl-style:
$ restclient get http://example.com/resource > output_body
$ restclient put http://example.com/resource < input_body
== Logging
To enable logging you can
* set RestClient.log with a ruby Logger
* or set an environment variable to avoid modifying the code (in this case you can use a file name, "stdout" or "stderr"):
$ RESTCLIENT_LOG=stdout path/to/my/program
Either produces logs like this:
RestClient.get "http://some/resource"
# => 200 OK | text/html 250 bytes
RestClient.put "http://some/resource", "payload"
# => 401 Unauthorized | application/xml 340 bytes
Note that these logs are valid Ruby, so you can paste them into the restclient
shell or a script to replay your sequence of rest calls.
== Proxy
All calls to RestClient, including Resources, will use the proxy specified by
RestClient.proxy:
RestClient.proxy = "http://proxy.example.com/"
RestClient.get "http://some/resource"
# => response from some/resource as proxied through proxy.example.com
Often the proxy url is set in an environment variable, so you can do this to
use whatever proxy the system is configured to use:
RestClient.proxy = ENV['http_proxy']
== Cookies
Request and Response objects know about HTTP cookies, and will automatically
extract and set headers for them as needed:
response = RestClient.get 'http://example.com/action_which_sets_session_id'
response.cookies
# => {"_applicatioN_session_id" => "1234"}
response2 = RestClient.post(
'http://localhost:3000/',
{:param1 => "foo"},
{:cookies => {:session_id => "1234"}}
)
# ...response body
== SSL Client Certificates
RestClient::Resource.new(
'https://example.com',
:ssl_client_cert => OpenSSL::X509::Certificate.new(File.read("cert.pem")),
:ssl_client_key => OpenSSL::PKey::RSA.new(File.read("key.pem"), "passphrase, if any"),
:ssl_ca_file => "ca_certificate.pem",
:verify_ssl => OpenSSL::SSL::VERIFY_PEER
).get
Self-signed certificates can be generated with the openssl command-line tool.
== Meta
Written by Adam Wiggins, major modifications by Blake Mizerany, maintained by Archiloque
Written by Adam Wiggins, major modifications by Blake Mizerany, maintained by Julien Kirch
Patches contributed by: Chris Anderson, Greg Borenstein, Ardekantur, Pedro Belo, Rafael Souza, Rick Olson, Aman Gupta, François Beausoleil and Nick Plante.
Patches contributed by many, including Chris Anderson, Greg Borenstein, Ardekantur, Pedro Belo, Rafael Souza, Rick Olson, Aman Gupta, François Beausoleil and Nick Plante.
Released under the MIT License: http://www.opensource.org/licenses/mit-license.php

View file

@ -3,9 +3,9 @@ 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.name = "rest-client-next"
s.description = "A simple HTTP and REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete."
s.summary = "Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions."
s.author = "Adam Wiggins"
s.email = "rest.client@librelist.com"
s.homepage = "http://github.com/archiloque/rest-client"

View file

@ -1 +1 @@
1.2.0
1.3.0

View file

@ -1,9 +1,26 @@
# 1.4.0
- Response is no more a String, and the mixin is replaced by an abstract_response, existing calls are redirected to response body with a warning.
The response change may be breaking in rare cases.
# 1.3.0
- a block can be used to process a request's result, this enable to handle custom error codes or paththrought (design by Cyril Rohr)
- cleaner log API, add a warning for some cases but should be compatible
- accept multiple "Set-Cookie" headers, see http://www.ietf.org/rfc/rfc2109.txt (patch provided by Cyril Rohr)
- remove "Content-Length" and "Content-Type" headers when following a redirection (patch provided by haarts)
- all http error codes have now a corresponding exception class and all of them contain the Reponse -> this means that the raised exception can be different
- changed "Content-Disposition: multipart/form-data" to "Content-Disposition: form-data" per RFC 2388 (patch provided by Kyle Crawford)
The only breaking change should be the exception classes, but as the new classes inherits from the existing ones, the breaking cases should be rare.
# 1.2.0
- formatting changed from tabs to spaces
- logged requests now include generated headers
- accept and content-type headers can now be specified using extentions: RestClient.post "http://example.com/resource", { 'x' => 1 }.to_json, :content_type => :json, :accept => :json
- should be 1.1.1 but renammed to 1.2.0 because 1.1.X versions has already been packaged on Debian
- should be 1.1.1 but renamed to 1.2.0 because 1.1.X versions has already been packaged on Debian
# 1.1.0

View file

@ -9,12 +9,12 @@ rescue LoadError => e
raise LoadError, "no such file to load -- net/https. Try running apt-get install libopenssl-ruby"
end
require File.dirname(__FILE__) + '/restclient/exceptions'
require File.dirname(__FILE__) + '/restclient/request'
require File.dirname(__FILE__) + '/restclient/mixin/response'
require File.dirname(__FILE__) + '/restclient/abstract_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'
@ -64,40 +64,38 @@ require File.dirname(__FILE__) + '/restclient/net_http_ext'
#
module RestClient
def self.get(url, headers={})
Request.execute(:method => :get, :url => url, :headers => headers)
def self.get(url, headers={}, &block)
Request.execute(:method => :get, :url => url, :headers => headers, &block)
end
def self.post(url, payload, headers={})
Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers)
def self.post(url, payload, headers={}, &block)
Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers, &block)
end
def self.put(url, payload, headers={})
Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers)
def self.put(url, payload, headers={}, &block)
Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers, &block)
end
def self.delete(url, headers={})
Request.execute(:method => :delete, :url => url, :headers => headers)
def self.delete(url, headers={}, &block)
Request.execute(:method => :delete, :url => url, :headers => headers, &block)
end
def self.head(url, headers={})
Request.execute(:method => :head, :url => url, :headers => headers)
def self.head(url, headers={}, &block)
Request.execute(:method => :head, :url => url, :headers => headers, &block)
end
class << self
attr_accessor :proxy
end
# Print log of RestClient calls. Value can be stdout, stderr, or a filename.
# Setup the log for RestClient calls.
# Value should be a logger but can 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
def self.log= log
if log.is_a? String
warn "[warning] You should set the log with a logger"
end
@@log = create_log log
end
def self.version
@ -105,4 +103,49 @@ module RestClient
return File.read(version_path).chomp if File.file?(version_path)
"0.0.0"
end
# Create a log that respond to << like a logger
# param can be 'stdout', 'stderr', a string (then we will log to that file) or a logger (then we return it)
def self.create_log param
if param
if param.is_a? String
if param == 'stdout'
stdout_logger = Class.new do
def << obj
STDOUT.puts obj
end
end
stdout_logger.new
elsif param == 'stderr'
stderr_logger = Class.new do
def << obj
STDERR.puts obj
end
end
stderr_logger.new
else
file_logger = Class.new do
attr_writer :target_file
def << obj
File.open(@target_file, 'a') { |f| f.puts obj }
end
end
logger = file_logger.new
logger.target_file = param
logger
end
else
param
end
end
end
@@env_log = create_log ENV['RESTCLIENT_LOG']
@@log = nil
def self.log # :nodoc:
@@env_log || @@log
end
end

View file

@ -0,0 +1,67 @@
module RestClient
class AbstractResponse
attr_reader :net_http_res
def initialize net_http_res
@net_http_res = net_http_res
end
# HTTP status code
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
# The raw headers.
def raw_headers
@raw_headers ||= @net_http_res.to_hash
end
# Hash of cookies extracted from response headers
def cookies
@cookies ||= (self.headers[:set_cookie] || []).inject({}) do |out, cookie_content|
# correctly parse comma-separated cookies containing HTTP dates (which also contain a comma)
cookie_content.split(/,\s*/).inject([""]) { |array, blob|
blob =~ /expires=.+?$/ ? array.push(blob) : array.last.concat(blob)
array
}.each do |cookie|
next if cookie.empty?
key, *val = cookie.split(";").first.split("=")
out[key] = val.join("=")
end
out
end
end
# Return the default behavior corresponding to the response code:
# the response itself for code in 200..206 and an exception in other cases
def return!
if (200..206).include? code
self
elsif Exceptions::EXCEPTIONS_MAP[code]
raise Exceptions::EXCEPTIONS_MAP[code], self
else
raise RequestFailed, self
end
end
def inspect
"#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n"
end
def AbstractResponse.beautify_headers(headers)
headers.inject({}) do |out, (key, value)|
out[key.gsub(/-/, '_').downcase.to_sym] = %w{set-cookie}.include?(key.downcase) ? value : value.first
out
end
end
end
end

View file

@ -1,76 +1,80 @@
module RestClient
STATUSES = {100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
400 => 'Bad Request',
401 => 'Unauthorized',
403 => 'Forbidden',
404 => 'Resource Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported'}
# This is the base RestClient exception class. Rescue it if you want to
# catch any exception that your request might raise
# You can get the status code by e.http_code, or see anything about the
# response via e.response.
# For example, the entire result body (which is
# probably an HTML error page) is e.response.
class Exception < RuntimeError
def message(default=nil)
self.class::ErrorMessage
end
end
attr_accessor :message, :response
# Base RestClient exception when there's a response available
class ExceptionWithResponse < Exception
attr_accessor :response
def initialize(response=nil)
def initialize response = nil
@response = response
end
def http_code
# return integer for compatibility
@response.code.to_i if @response
end
def http_body
RestClient::Request.decode(@response['content-encoding'], @response.body) if @response
@response.body
end
end
# A redirect was encountered; caught by execute to retry with the new url.
class Redirect < Exception
ErrorMessage = "Redirect"
attr_accessor :url
def initialize(url)
@url = url
def inspect
"#{self.class} : #{http_code} #{message}"
end
end
class NotModified < ExceptionWithResponse
ErrorMessage = 'NotModified'
# Compatibility
class ExceptionWithResponse < Exception
end
# Authorization is required to access the resource specified.
class Unauthorized < ExceptionWithResponse
ErrorMessage = 'Unauthorized'
end
# No resource was found at the given URL.
class ResourceNotFound < ExceptionWithResponse
ErrorMessage = 'Resource not found'
end
# 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
ErrorMessage = 'Server broke connection'
end
# The server took too long to respond.
class RequestTimeout < Exception
ErrorMessage = 'Request timed out'
end
# The request failed, meaning the remote HTTP server returned a code other
# than success, unauthorized, or redirect.
#
# The exception message attempts to extract the error from the XML, using
# format returned by Rails: <errors><error>some message</error></errors>
#
# You can get the status code by e.http_code, or see anything about the
# response via e.response. For example, the entire result body (which is
# probably an HTML error page) is e.response.body.
# The request failed with an error code not managed by the code
class RequestFailed < ExceptionWithResponse
def message
"HTTP status code #{http_code}"
end
@ -79,6 +83,45 @@ module RestClient
message
end
end
# We will a create an exception for each status code, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
module Exceptions
# Map http status codes to the corresponding exception class
EXCEPTIONS_MAP = {}
end
STATUSES.each_pair do |code, message|
# Compatibility
superclass = ([304, 401, 404].include? code) ? ExceptionWithResponse : RequestFailed
klass = Class.new(superclass) do
send(:define_method, :message) {message}
end
klass_constant = const_set message.gsub(/ /, '').gsub(/-/, ''), klass
Exceptions::EXCEPTIONS_MAP[code] = klass_constant
end
# A redirect was encountered; caught by execute to retry with the new url.
class Redirect < Exception
message = 'Redirect'
attr_accessor :url
def initialize(url)
@url = url
end
end
# 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
message = 'Server broke connection'
end
end
# backwards compatibility

View file

@ -1,48 +0,0 @@
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
# The raw headers.
def raw_headers
@raw_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(/-/, '_').downcase.to_sym] = value.first
out
end
end
end
end
end
end

View file

@ -91,7 +91,7 @@ module RestClient
end
def short_inspect
(size > 100 ? "#{size} byte length" : inspect)
(size > 100 ? "#{size} byte(s) length" : inspect)
end
end
@ -141,7 +141,7 @@ module RestClient
end
def create_regular_field(s, k, v)
s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"")
s.write("Content-Disposition: form-data; name=\"#{k}\"")
s.write(EOL)
s.write(EOL)
s.write(v)
@ -149,7 +149,7 @@ module RestClient
def create_file_field(s, k, v)
begin
s.write("Content-Disposition: multipart/form-data; name=\"#{k}\"; filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
s.write("Content-Disposition: form-data; name=\"#{k}\"; filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
s.write(EOL)
while data = v.read(8124)

View file

@ -1,5 +1,3 @@
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
@ -11,13 +9,12 @@ module RestClient
# 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
class RawResponse < AbstractResponse
attr_reader :file
def initialize(tempfile, net_http_res)
@net_http_res = net_http_res
def initialize tempfile, net_http_res
super net_http_res
@file = tempfile
end
@ -26,5 +23,9 @@ module RestClient
@file.read
end
def size
File.size file
end
end
end

View file

@ -20,16 +20,18 @@ module RestClient
# * :timeout and :open_timeout
# * :ssl_client_cert, :ssl_client_key, :ssl_ca_file
class Request
attr_reader :method, :url, :payload, :headers, :processed_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
def self.execute(args, &block)
new(args).execute &block
end
def initialize(args)
def initialize args
@method = args[:method] or raise ArgumentError, "must pass :method"
@url = args[:url] or raise ArgumentError, "must pass :url"
@headers = args[:headers] || {}
@ -48,23 +50,25 @@ module RestClient
@processed_headers = make_headers headers
end
def execute
execute_inner
def execute &block
execute_inner &block
rescue Redirect => e
@processed_headers.delete "Content-Length"
@processed_headers.delete "Content-Type"
@url = e.url
@method = :get
@payload = nil
execute
execute &block
end
def execute_inner
def execute_inner &block
uri = parse_url_with_auth(url)
transmit uri, net_http_request_class(method).new(uri.request_uri, processed_headers), payload
transmit uri, net_http_request_class(method).new(uri.request_uri, processed_headers), payload, &block
end
def make_headers user_headers
unless @cookies.empty?
user_headers[:cookie] = @cookies.map {|key, val| "#{key.to_s}=#{val}" }.join('; ')
user_headers[:cookie] = @cookies.map {|(key, val)| "#{key.to_s}=#{val}" }.sort.join(",")
end
headers = default_headers.merge(user_headers).inject({}) do |final, (key, value)|
@ -73,13 +77,13 @@ module RestClient
target_value = value.to_s
final[target_key] = MIME::Types.type_for_extension target_value
elsif 'ACCEPT' == target_key.upcase
# Accept can be composed of several comma-separated values
if value.is_a? Array
target_values = value
else
target_values = value.to_s.split ','
end
final[target_key] = target_values.map{ |ext| MIME::Types.type_for_extension(ext.to_s.strip)}.join(', ')
# Accept can be composed of several comma-separated values
if value.is_a? Array
target_values = value
else
target_values = value.to_s.split ','
end
final[target_key] = target_values.map{ |ext| MIME::Types.type_for_extension(ext.to_s.strip)}.join(', ')
else
final[target_key] = value.to_s
end
@ -132,8 +136,8 @@ module RestClient
end
end
def transmit(uri, req, payload)
setup_credentials(req)
def transmit uri, req, payload, &block
setup_credentials req
net = net_http_class.new(uri.host, uri.port)
net.use_ssl = uri.is_a?(URI::HTTPS)
@ -148,20 +152,12 @@ module RestClient
net.read_timeout = @timeout if @timeout
net.open_timeout = @open_timeout if @open_timeout
display_log request_log
log_request
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
Response.new(nil, res)
end
log_response res
process_result res, &block
end
rescue EOFError
raise RestClient::ServerBrokeConnection
@ -181,14 +177,16 @@ module RestClient
@tf = Tempfile.new("rest-client")
size, total = 0, http_response.header['Content-Length'].to_i
http_response.read_body do |chunk|
@tf.write(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])
if RestClient.log
if size == 0
RestClient.log << "#{@method} #{@url} done (0 length file\n)"
elsif total == 0
RestClient.log << "#{@method} #{@url} (zero content length)\n"
else
RestClient.log << "#{@method} #{@url} %d%% done (%d of %d)\n" % [(size * 100) / total, size, total]
end
end
end
@tf.close
@ -199,13 +197,17 @@ module RestClient
http_response
end
def process_result(res)
if res.code =~ /\A2\d{2}\z/
def process_result res
if @raw_response
# 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
response = RawResponse.new(@tf, res)
else
response = Response.new(Request.decode(res['content-encoding'], res.body), res)
end
code = res.code.to_i
if (301..303).include? code
url = res.header['Location']
if url !~ /^http/
@ -213,53 +215,40 @@ module RestClient
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
if block_given?
yield response
else
response.return!
end
end
end
def self.decode(content_encoding, body)
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)
Zlib::Inflate.new.inflate body
else
body
end
end
def request_log
def log_request
if RestClient.log
out = []
out << "RestClient.#{method} #{url.inspect}"
out << "headers: #{processed_headers.inspect}"
out << "paylod: #{payload.short_inspect}" if payload
out.join(', ')
out << payload.short_inspect if payload
out << processed_headers.inspect.gsub(/^\{/, '').gsub(/\}$/, '')
RestClient.log << out.join(', ') + "\n"
end
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 }
def log_response res
if RestClient.log
size = @raw_response ? File.size(@tf.path) : (res.body.nil? ? 0 : res.body.size)
RestClient.log << "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes\n"
end
end
@ -274,7 +263,7 @@ module MIME
# Return the first found content-type for a value considered as an extension or the value itself
def type_for_extension ext
candidates = @extension_index[ext]
candidates = @extension_index[ext]
candidates.empty? ? ext : candidates[0].content_type
end

View file

@ -34,10 +34,11 @@ module RestClient
# site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
#
class Resource
attr_reader :url, :options
attr_reader :url, :options, :block
def initialize(url, options={}, backwards_compatibility=nil)
def initialize(url, options={}, backwards_compatibility=nil, &block)
@url = url
@block = block
if options.class == Hash
@options = options
else # compatibility with previous versions
@ -45,38 +46,38 @@ module RestClient
end
end
def get(additional_headers={}, &b)
def get(additional_headers={}, &block)
headers = (options[:headers] || {}).merge(additional_headers)
Request.execute(options.merge(
:method => :get,
:url => url,
:headers => headers), &b)
:headers => headers), &(block || @block))
end
def post(payload, additional_headers={}, &b)
def post(payload, additional_headers={}, &block)
headers = (options[:headers] || {}).merge(additional_headers)
Request.execute(options.merge(
:method => :post,
:url => url,
:payload => payload,
:headers => headers), &b)
:headers => headers), &(block || @block))
end
def put(payload, additional_headers={}, &b)
def put(payload, additional_headers={}, &block)
headers = (options[:headers] || {}).merge(additional_headers)
Request.execute(options.merge(
:method => :put,
:url => url,
:payload => payload,
:headers => headers), &b)
:headers => headers), &(block || @block))
end
def delete(additional_headers={}, &b)
def delete(additional_headers={}, &block)
headers = (options[:headers] || {}).merge(additional_headers)
Request.execute(options.merge(
:method => :delete,
:url => url,
:headers => headers), &b)
:headers => headers), &(block || @block))
end
def to_s

View file

@ -1,19 +1,31 @@
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
# A Response from RestClient, you can access the response body, the code or the headers.
#
class Response < AbstractResponse
def initialize(string, net_http_res)
@net_http_res = net_http_res
super(string || "")
attr_reader :body
def initialize body, net_http_res
super net_http_res
@body = body || ""
end
def method_missing symbol, *args
if body.respond_to? symbol
warn "[warning] The Response is no more a String, please update your code"
body.send symbol, *args
else
super
end
end
def to_s
body.to_s
end
def size
body.size
end
end

View file

@ -2,16 +2,16 @@
Gem::Specification.new do |s|
s.name = %q{rest-client}
s.version = "1.2.0"
s.version = "1.3.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Adam Wiggins", "Archiloque"]
s.date = %q{2010-01-3}
s.authors = ["Adam Wiggins", "Julien Kirch"]
s.date = %q{2010-01-25}
s.default_executable = %q{restclient}
s.description = %q{A simple REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete.}
s.description = %q{A simple Simple HTTP and REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete.}
s.email = %q{rest.client@librelist.com}
s.executables = ["restclient"]
s.extra_rdoc_files = [
"README.rdoc"
"README.rdoc", "history.md"
]
s.files = [
"README.rdoc",
@ -21,7 +21,7 @@ Gem::Specification.new do |s|
"lib/rest_client.rb",
"lib/restclient.rb",
"lib/restclient/exceptions.rb",
"lib/restclient/mixin/response.rb",
"lib/restclient/abstract_response.rb",
"lib/restclient/net_http_ext.rb",
"lib/restclient/payload.rb",
"lib/restclient/raw_response.rb",
@ -30,8 +30,9 @@ Gem::Specification.new do |s|
"lib/restclient/response.rb",
"spec/base.rb",
"spec/exceptions_spec.rb",
"spec/integration_spec.rb",
"spec/master_shake.jpg",
"spec/mixin/response_spec.rb",
"spec/abstract_response_spec.rb",
"spec/payload_spec.rb",
"spec/raw_response_spec.rb",
"spec/request_spec.rb",
@ -48,7 +49,8 @@ Gem::Specification.new do |s|
s.test_files = [
"spec/base.rb",
"spec/exceptions_spec.rb",
"spec/mixin/response_spec.rb",
"spec/integration_spec.rb",
"spec/abstract_response_spec.rb",
"spec/payload_spec.rb",
"spec/raw_response_spec.rb",
"spec/request_spec.rb",

View file

@ -1,18 +1,9 @@
require File.dirname(__FILE__) + '/../base'
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
describe RestClient::AbstractResponse do
before do
@net_http_res = mock('net http response')
@response = MockResponse.new('abc', @net_http_res)
@response = RestClient::AbstractResponse.new(@net_http_res)
end
it "fetches the numeric response code" do

View file

@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/base'
describe RestClient::Exception do
it "sets the exception message to ErrorMessage" do
RestClient::ResourceNotFound.new.message.should == 'Resource not found'
RestClient::ResourceNotFound.new.message.should == 'Resource Not Found'
end
it "contains exceptions in RestClient" do
@ -29,10 +29,8 @@ describe RestClient::RequestFailed do
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'
RestClient::RequestFailed.new(@response).http_code.should == 502
RestClient::RequestFailed.new(@response).message.should == 'HTTP status code 502'
end
it "shows the status code in the message" do

38
spec/integration_spec.rb Normal file
View file

@ -0,0 +1,38 @@
require File.dirname(__FILE__) + '/base'
require 'webmock/rspec'
include WebMock
describe RestClient do
it "a simple request" do
body = 'abc'
stub_request(:get, "www.example.com").to_return(:body => body, :status => 200)
response = RestClient.get "www.example.com"
response.code.should == 200
response.body.should == body
end
it "a simple request with gzipped content" do
stub_request(:get, "www.example.com").with(:headers => { 'Accept-Encoding' => 'gzip, deflate' }).to_return(:body => "\037\213\b\b\006'\252H\000\003t\000\313T\317UH\257\312,HM\341\002\000G\242(\r\v\000\000\000", :status => 200, :headers => { 'Content-Encoding' => 'gzip' } )
response = RestClient.get "www.example.com"
response.code.should == 200
response.body.should == "i'm gziped\n"
end
it "a 404" do
body = "Ho hai ! I'm not here !"
stub_request(:get, "www.example.com").to_return(:body => body, :status => 404)
begin
RestClient.get "www.example.com"
raise
rescue RestClient::ResourceNotFound => e
e.http_code.should == 404
e.response.code.should == 404
e.response.body.should == body
e.http_body.should == body
end
end
end

View file

@ -48,11 +48,11 @@ describe RestClient::Payload do
m = RestClient::Payload::Multipart.new([[:bar, "baz"], [:foo, "bar"]])
m.to_s.should == <<-EOS
--#{m.boundary}\r
Content-Disposition: multipart/form-data; name="bar"\r
Content-Disposition: form-data; name="bar"\r
\r
baz\r
--#{m.boundary}\r
Content-Disposition: multipart/form-data; name="foo"\r
Content-Disposition: form-data; name="foo"\r
\r
bar\r
--#{m.boundary}--\r
@ -64,7 +64,7 @@ bar\r
m = RestClient::Payload::Multipart.new({:foo => f})
m.to_s.should == <<-EOS
--#{m.boundary}\r
Content-Disposition: multipart/form-data; name="foo"; filename="master_shake.jpg"\r
Content-Disposition: form-data; name="foo"; filename="master_shake.jpg"\r
Content-Type: image/jpeg\r
\r
#{IO.read(f.path)}\r
@ -79,7 +79,7 @@ Content-Type: image/jpeg\r
m = RestClient::Payload::Multipart.new({:foo => f})
m.to_s.should == <<-EOS
--#{m.boundary}\r
Content-Disposition: multipart/form-data; name="foo"; filename="foo.txt"\r
Content-Disposition: form-data; name="foo"; filename="foo.txt"\r
Content-Type: text/plain\r
\r
#{IO.read(f.path)}\r
@ -91,7 +91,7 @@ Content-Type: text/plain\r
m = RestClient::Payload::Multipart.new({:bar => {:baz => "foo"}})
m.to_s.should == <<-EOS
--#{m.boundary}\r
Content-Disposition: multipart/form-data; name="bar[baz]"\r
Content-Disposition: form-data; name="bar[baz]"\r
\r
foo\r
--#{m.boundary}--\r
@ -103,7 +103,7 @@ foo\r
m = RestClient::Payload::Multipart.new({:foo => {:bar => f}})
m.to_s.should == <<-EOS
--#{m.boundary}\r
Content-Disposition: multipart/form-data; name="foo[bar]"; filename="foo.txt"\r
Content-Disposition: form-data; name="foo[bar]"; filename="foo.txt"\r
Content-Type: text/plain\r
\r
#{IO.read(f.path)}\r

View file

@ -15,27 +15,29 @@ describe RestClient::Request do
@net.stub!(:start).and_yield(@http)
@net.stub!(:use_ssl=)
@net.stub!(:verify_mode=)
RestClient.log = 'test.log'
RestClient.log = nil
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
describe "compression" do
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 "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 "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"
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
end
it "processes a successful result" do
@ -43,7 +45,8 @@ describe RestClient::Request do
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'
@request.process_result(res).body.should == 'body'
@request.process_result(res).to_s.should == 'body'
end
it "doesn't classify successful requests as failed" do
@ -66,76 +69,83 @@ describe RestClient::Request do
@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
describe "user - password" do
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'
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
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 = RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {:session_id => '1', :user_id => "someone" })
@request.should_receive(:default_headers).and_return({'foo' => 'bar'})
headers = @request.make_headers({}).should == { 'Foo' => 'bar', 'Cookie' => 'session_id=1'}
headers = @request.make_headers({}).should == { 'Foo' => 'bar', 'Cookie' => 'session_id=1,user_id=someone'}
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' })
headers = @request.make_headers('3' => '4')
headers.should have_key('1')
headers['1'].should == '2'
headers.should have_key('3')
headers['3'].should == '4'
describe "user headers" do
it "merges user headers with the default headers" do
@request.should_receive(:default_headers).and_return({ '1' => '2' })
headers = @request.make_headers('3' => '4')
headers.should have_key('1')
headers['1'].should == '2'
headers.should have_key('3')
headers['3'].should == '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' })
headers = @request.make_headers('1' => '3')
headers.should have_key('1')
headers['1'].should == '3'
end
end
it "prefers the user header when the same header exists in the defaults" do
@request.should_receive(:default_headers).and_return({ '1' => '2' })
headers = @request.make_headers('1' => '3')
headers.should have_key('1')
headers['1'].should == '3'
end
describe "header symbols" do
it "converts header symbols from :content_type to 'Content-type'" do
@request.should_receive(:default_headers).and_return({})
headers = @request.make_headers(:content_type => 'abc')
headers.should have_key('Content-type')
headers['Content-type'].should == 'abc'
end
it "converts header symbols from :content_type to 'Content-type'" do
@request.should_receive(:default_headers).and_return({})
headers = @request.make_headers(:content_type => 'abc')
headers.should have_key('Content-type')
headers['Content-type'].should == 'abc'
end
it "converts content-type from extension to real content-type" do
@request.should_receive(:default_headers).and_return({})
headers = @request.make_headers(:content_type => 'json')
headers.should have_key('Content-type')
headers['Content-type'].should == 'application/json'
end
it "converts content-type from extension to real content-type" do
@request.should_receive(:default_headers).and_return({})
headers = @request.make_headers(:content_type => 'json')
headers.should have_key('Content-type')
headers['Content-type'].should == 'application/json'
end
it "converts accept from extension(s) to real content-type(s)" do
@request.should_receive(:default_headers).and_return({})
headers = @request.make_headers(:accept => 'json, mp3')
headers.should have_key('Accept')
headers['Accept'].should == 'application/json, audio/mpeg'
it "converts accept from extension(s) to real content-type(s)" do
@request.should_receive(:default_headers).and_return({})
headers = @request.make_headers(:accept => 'json, mp3')
headers.should have_key('Accept')
headers['Accept'].should == 'application/json, audio/mpeg'
@request.should_receive(:default_headers).and_return({})
headers = @request.make_headers(:accept => :json)
headers.should have_key('Accept')
headers['Accept'].should == 'application/json'
end
@request.should_receive(:default_headers).and_return({})
headers = @request.make_headers(:accept => :json)
headers.should have_key('Accept')
headers['Accept'].should == 'application/json'
end
it "converts header values to strings" do
@request.make_headers('A' => 1)['A'].should == '1'
it "converts header values to strings" do
@request.make_headers('A' => 1)['A'].should == '1'
end
end
it "executes by constructing the Net::HTTP object, headers, and payload and calling transmit" do
@ -150,39 +160,31 @@ describe RestClient::Request do
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
describe "payload" do
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 "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 "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 "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')
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
end
it "set urlencoded content_type header on hash payloads" do
@ -190,31 +192,33 @@ describe RestClient::Request do
@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)
describe "credentials" do
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.stub!(:user).and_return('joe')
@request.stub!(:password).and_return('mypass')
@request.should_receive(:setup_credentials).with('req')
@request.transmit(@uri, 'req', nil)
end
@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 "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)
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
end
it "catches EOFError and shows the more informative ServerBrokeConnection" do
@ -234,275 +238,297 @@ describe RestClient::Request do
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
describe "redirection" do
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'}, :[] => ['content-encoding' => ''], :body => '' )
lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://new/resource'}
end
@request.execute
it "handles redirects with relative paths" do
res = mock('response', :code => '301', :header => { 'Location' => 'index' }, :[] => ['content-encoding' => ''], :body => '' )
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' }, :[] => ['content-encoding' => ''], :body => '' )
lambda { @request.process_result(res) }.should raise_error(RestClient::Redirect) { |e| e.url.should == 'http://some/index' }
end
it "uses GET and clears payload and removes possible harmful headers 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.processed_headers.should_not have_key("Content-Length")
@request.url.should == url
@request.method.should == :get
@request.payload.should be_nil
end
@request.execute
end
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)
describe "exception" do
it "raises Unauthorized when the response is 401" do
res = mock('response', :code => '401', :[] => ['content-encoding' => ''], :body => '' )
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', :[] => ['content-encoding' => ''], :body => '' )
lambda { @request.process_result(res) }.should raise_error(RestClient::ResourceNotFound)
end
it "raises RequestFailed otherwise" do
res = mock('response', :code => '500', :[] => ['content-encoding' => ''], :body => '' )
lambda { @request.process_result(res) }.should raise_error(RestClient::InternalServerError)
end
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)
describe "block usage" do
it "returns what asked to" do
res = mock('response', :code => '401', :[] => ['content-encoding' => ''], :body => '' )
@request.process_result(res){|response| "foo"}.should == "foo"
end
end
it "raises RequestFailed otherwise" do
res = mock('response', :code => '500')
lambda { @request.process_result(res) }.should raise_error(RestClient::RequestFailed)
describe "proxy" do
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
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
describe "logging" do
it "logs a get request" do
log = RestClient.log = []
RestClient::Request.new(:method => :get, :url => 'http://url').log_request
['RestClient.get "http://url", "Accept-encoding"=>"gzip, deflate", "Accept"=>"*/*; q=0.5, application/xml"' + "\n",
'RestClient.get "http://url", "Accept"=>"*/*; q=0.5, application/xml", "Accept-encoding"=>"gzip, deflate"' + "\n"].should include(log[0])
end
it "logs a get request" do
['RestClient.get "http://url", headers: {"Accept-encoding"=>"gzip, deflate", "Accept"=>"*/*; q=0.5, application/xml"}',
'RestClient.get "http://url", headers: {"Accept"=>"*/*; q=0.5, application/xml", "Accept-encoding"=>"gzip, deflate}'].should include
RestClient::Request.new(:method => :get, :url => 'http://url').request_log
end
it "logs a post request with a small payload" do
log = RestClient.log = []
RestClient::Request.new(:method => :post, :url => 'http://url', :payload => 'foo').log_request
['RestClient.post "http://url", "foo", "Accept-encoding"=>"gzip, deflate", "Content-Length"=>"3", "Accept"=>"*/*; q=0.5, application/xml"' + "\n",
'RestClient.post "http://url", "foo", "Accept"=>"*/*; q=0.5, application/xml", "Accept-encoding"=>"gzip, deflate", "Content-Length"=>"3"' + "\n"].should include(log[0])
end
it "logs a post request with a small payload" do
['RestClient.post "http://url", headers: {"Accept-encoding"=>"gzip, deflate", "Content-Length"=>"3", "Accept"=>"*/*; q=0.5, application/xml"}, paylod: "foo"',
'RestClient.post "http://url", headers: {"Accept"=>"*/*; q=0.5, application/xml", "Accept-encoding"=>"gzip, deflate", "Content-Length"=>"3"}, paylod: "foo"'].should include
RestClient::Request.new(:method => :post, :url => 'http://url', :payload => 'foo').request_log
end
it "logs a post request with a large payload" do
log = RestClient.log = []
RestClient::Request.new(:method => :post, :url => 'http://url', :payload => ('x' * 1000)).log_request
['RestClient.post "http://url", 1000 byte(s) length, "Accept-encoding"=>"gzip, deflate", "Content-Length"=>"1000", "Accept"=>"*/*; q=0.5, application/xml"' + "\n",
'RestClient.post "http://url", 1000 byte(s) length, "Accept"=>"*/*; q=0.5, application/xml", "Accept-encoding"=>"gzip, deflate", "Content-Length"=>"1000"' + "\n"].should include(log[0])
end
it "logs a post request with a large payload" do
['RestClient.post "http://url", headers: {"Accept-encoding"=>"gzip, deflate", "Content-Length"=>"1000", "Accept"=>"*/*; q=0.5, application/xml"}, paylod: 1000 byte length',
'RestClient.post "http://url", headers: {"Accept"=>"*/*; q=0.5, application/xml", "Accept-encoding"=>"gzip, deflate", "Content-Length"=>"1000"}, paylod: 1000 byte length'].should include
RestClient::Request.new(:method => :post, :url => 'http://url', :payload => ('x' * 1000)).request_log
end
it "logs input headers as a hash" do
log = RestClient.log = []
RestClient::Request.new(:method => :get, :url => 'http://url', :headers => { :accept => 'text/plain' }).log_request
['RestClient.get "http://url", "Accept-encoding"=>"gzip, deflate", "Accept"=>"text/plain"' + "\n",
'RestClient.get "http://url", "Accept"=>"text/plain", "Accept-encoding"=>"gzip, deflate"' + "\n"].should include(log[0])
end
it "logs input headers as a hash" do
['RestClient.get "http://url", headers: {"Accept-encoding"=>"gzip, deflate", "Accept"=>"text/plain"}',
'RestClient.get "http://url", headers: {"Accept"=>"text/plain", "Accept-encoding"=>"gzip, deflate"}'].should include
RestClient::Request.new(:method => :get, :url => 'http://url', :headers => { :accept => 'text/plain' })
end
it "logs a response including the status code, content type, and result body size in bytes" do
log = RestClient.log = []
res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
res.stub!(:[]).with('Content-type').and_return('text/html')
@request.log_response res
log[0].should == "# => 200 OK | text/html 4 bytes\n"
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
log = RestClient.log = []
res = mock('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd')
res.stub!(:[]).with('Content-type').and_return(nil)
@request.log_response res
log[0].should == "# => 200 OK | 4 bytes\n"
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"
it "logs a response with a nil body" do
log = RestClient.log = []
res = mock('result', :code => '200', :class => Net::HTTPOK, :body => nil)
res.stub!(:[]).with('Content-type').and_return('text/html; charset=utf-8')
@request.log_response res
log[0].should == "# => 200 OK | text/html 0 bytes\n"
end
end
it "strips the charset from the response content type" do
log = RestClient.log = []
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"
@request.log_response res
log[0].should == "# => 200 OK | text/html 4 bytes\n"
end
it "displays the log to stdout" do
RestClient.stub!(:log).and_return('stdout')
STDOUT.should_receive(:puts).with('xyz')
@request.display_log('xyz')
describe "timeout" do
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
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
describe "ssl" do
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 "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 "should default to not verifying ssl certificates" do
@request.verify_ssl.should == false
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)
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
@net.should_receive(:read_timeout=).with(123)
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
@request.transmit(@uri, 'req', nil)
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 "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)
it "should default to not having an ssl_client_cert" do
@request.ssl_client_cert.should be(nil)
end
@net.should_receive(:open_timeout=).with(123)
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
@request.transmit(@uri, 'req', nil)
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 verifying ssl certificates" do
@request.verify_ssl.should == false
end
it "should default to not having an ssl_client_key" do
@request.ssl_client_key.should be(nil)
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 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 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 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 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_ca_file" do
@request.ssl_ca_file.should be(nil)
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_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 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')
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
it "should still return a response object for 204 No Content responses" do
@ -512,7 +538,7 @@ describe RestClient::Request do
:payload => 'payload'
)
net_http_res = Net::HTTPNoContent.new("", "204", "No Content")
net_http_res.stub(:read_body).and_return(nil)
net_http_res.stub!(:read_body).and_return(nil)
@http.should_receive(:request).and_return(@request.fetch_body(net_http_res))
response = @request.transmit(@uri, 'req', 'payload')
response.should_not be_nil

View file

@ -1,5 +1,8 @@
require File.dirname(__FILE__) + '/base'
require 'webmock/rspec'
include WebMock
describe RestClient::Resource do
before do
@resource = RestClient::Resource.new('http://some/resource', :user => 'jane', :password => 'mypass', :headers => { 'X-Something' => '1'})
@ -72,4 +75,25 @@ describe RestClient::Resource do
it "prints its url with to_s" do
RestClient::Resource.new('x').to_s.should == 'x'
end
describe 'block' do
it 'can use block when creating the resource' do
stub_request(:get, 'www.example.com').to_return(:body => '', :status => 404)
resource = RestClient::Resource.new('www.example.com'){|response| 'foo'}
resource.get.should == 'foo'
end
it 'can use block when executing the resource' do
stub_request(:get, 'www.example.com').to_return(:body => '', :status => 404)
resource = RestClient::Resource.new('www.example.com')
resource.get{|response| 'foo'}.should == 'foo'
end
it 'execution block override resource block' do
stub_request(:get, 'www.example.com').to_return(:body => '', :status => 404)
resource = RestClient::Resource.new('www.example.com'){|response| 'foo'}
resource.get{|response| 'bar'}.should == 'bar'
end
end
end

View file

@ -7,15 +7,68 @@ describe RestClient::Response do
end
it "behaves like string" do
@response.should == 'abc'
@response.should.to_s == '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 == ""
RestClient::Response.new(nil, @net_http_res).should.to_s == ""
end
it "test headers and raw headers" do
@response.raw_headers["Status"][0].should == "200 OK"
@response.headers[:status].should == "200 OK"
end
describe "cookie processing" do
it "should correctly deal with one Set-Cookie header with one cookie inside" do
net_http_res = mock('net http response', :to_hash => {"etag" => ["\"e1ac1a2df945942ef4cac8116366baad\""], "set-cookie" => ["main_page=main_page_no_rewrite; path=/; expires=Tue, 20-Jan-2015 15:03:14 GMT"]})
response = RestClient::Response.new('abc', net_http_res)
response.headers[:set_cookie].should == ["main_page=main_page_no_rewrite; path=/; expires=Tue, 20-Jan-2015 15:03:14 GMT"]
response.cookies.should == { "main_page" => "main_page_no_rewrite" }
end
it "should correctly deal with multiple cookies [multiple Set-Cookie headers]" do
net_http_res = mock('net http response', :to_hash => {"etag" => ["\"e1ac1a2df945942ef4cac8116366baad\""], "set-cookie" => ["main_page=main_page_no_rewrite; path=/; expires=Tue, 20-Jan-2015 15:03:14 GMT", "remember_me=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT", "user=somebody; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"]})
response = RestClient::Response.new('abc', net_http_res)
response.headers[:set_cookie].should == ["main_page=main_page_no_rewrite; path=/; expires=Tue, 20-Jan-2015 15:03:14 GMT", "remember_me=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT", "user=somebody; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"]
response.cookies.should == {
"main_page" => "main_page_no_rewrite",
"remember_me" => "",
"user" => "somebody"
}
end
it "should correctly deal with multiple cookies [one Set-Cookie header with multiple cookies]" do
net_http_res = mock('net http response', :to_hash => {"etag" => ["\"e1ac1a2df945942ef4cac8116366baad\""], "set-cookie" => ["main_page=main_page_no_rewrite; path=/; expires=Tue, 20-Jan-2015 15:03:14 GMT, remember_me=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT, user=somebody; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"]})
response = RestClient::Response.new('abc', net_http_res)
response.cookies.should == {
"main_page" => "main_page_no_rewrite",
"remember_me" => "",
"user" => "somebody"
}
end
end
describe "exceptions processing" do
it "should return itself for normal codes" do
(200..206).each do |code|
net_http_res = mock('net http response', :code => '200')
response = RestClient::Response.new('abc', net_http_res)
response.return!
end
end
it "should throw an exception for other codes" do
RestClient::Exceptions::EXCEPTIONS_MAP.each_key do |code|
unless (200..206).include? code
net_http_res = mock('net http response', :code => code.to_i)
response = RestClient::Response.new('abc', net_http_res)
lambda { response.return!}.should raise_error
end
end
end
end
end

View file

@ -33,21 +33,31 @@ describe RestClient 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'
it "uses << if the log is not a string" do
log = RestClient.log = []
log.should_receive(:<<).with('xyz')
RestClient.log << 'xyz'
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'
it "displays the log to stdout" do
RestClient.log = 'stdout'
STDOUT.should_receive(:puts).with('xyz')
RestClient.log << 'xyz'
end
it "returns nil (no logging) if neither are set (default)" do
ENV.stub!(:[]).with('RESTCLIENT_LOG').and_return(nil)
RestClient.log.should == nil
it "displays the log to stderr" do
RestClient.log = 'stderr'
STDERR.should_receive(:puts).with('xyz')
RestClient.log << 'xyz'
end
it "append the log to the requested filename" do
RestClient.log = '/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')
RestClient.log << 'xyz'
end
end
end