rest-client 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rest-client might be problematic. Click here for more details.
- data/README.rdoc +269 -0
- data/Rakefile +69 -0
- data/VERSION +1 -0
- data/bin/restclient +87 -0
- data/history.md +85 -0
- data/lib/rest-client.rb +2 -0
- data/lib/rest_client.rb +2 -0
- data/lib/restclient.rb +161 -0
- data/lib/restclient/abstract_response.rb +89 -0
- data/lib/restclient/exceptions.rb +172 -0
- data/lib/restclient/net_http_ext.rb +21 -0
- data/lib/restclient/payload.rb +220 -0
- data/lib/restclient/raw_response.rb +34 -0
- data/lib/restclient/request.rb +300 -0
- data/lib/restclient/resource.rb +147 -0
- data/lib/restclient/response.rb +24 -0
- data/spec/abstract_response_spec.rb +67 -0
- data/spec/base.rb +10 -0
- data/spec/exceptions_spec.rb +79 -0
- data/spec/integration/certs/equifax.crt +19 -0
- data/spec/integration/certs/verisign.crt +14 -0
- data/spec/integration/request_spec.rb +25 -0
- data/spec/integration_spec.rb +38 -0
- data/spec/master_shake.jpg +0 -0
- data/spec/payload_spec.rb +219 -0
- data/spec/raw_response_spec.rb +17 -0
- data/spec/request2_spec.rb +17 -0
- data/spec/request_spec.rb +529 -0
- data/spec/resource_spec.rb +99 -0
- data/spec/response_spec.rb +151 -0
- data/spec/restclient_spec.rb +63 -0
- metadata +124 -0
data/lib/rest-client.rb
ADDED
data/lib/rest_client.rb
ADDED
data/lib/restclient.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'zlib'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'net/https'
|
7
|
+
rescue LoadError => e
|
8
|
+
raise e unless RUBY_PLATFORM =~ /linux/
|
9
|
+
raise LoadError, "no such file to load -- net/https. Try running apt-get install libopenssl-ruby"
|
10
|
+
end
|
11
|
+
|
12
|
+
require File.dirname(__FILE__) + '/restclient/exceptions'
|
13
|
+
require File.dirname(__FILE__) + '/restclient/request'
|
14
|
+
require File.dirname(__FILE__) + '/restclient/abstract_response'
|
15
|
+
require File.dirname(__FILE__) + '/restclient/response'
|
16
|
+
require File.dirname(__FILE__) + '/restclient/raw_response'
|
17
|
+
require File.dirname(__FILE__) + '/restclient/resource'
|
18
|
+
require File.dirname(__FILE__) + '/restclient/payload'
|
19
|
+
require File.dirname(__FILE__) + '/restclient/net_http_ext'
|
20
|
+
|
21
|
+
# This module's static methods are the entry point for using the REST client.
|
22
|
+
#
|
23
|
+
# # GET
|
24
|
+
# xml = RestClient.get 'http://example.com/resource'
|
25
|
+
# jpg = RestClient.get 'http://example.com/resource', :accept => 'image/jpg'
|
26
|
+
#
|
27
|
+
# # authentication and SSL
|
28
|
+
# RestClient.get 'https://user:password@example.com/private/resource'
|
29
|
+
#
|
30
|
+
# # POST or PUT with a hash sends parameters as a urlencoded form body
|
31
|
+
# RestClient.post 'http://example.com/resource', :param1 => 'one'
|
32
|
+
#
|
33
|
+
# # nest hash parameters
|
34
|
+
# RestClient.post 'http://example.com/resource', :nested => { :param1 => 'one' }
|
35
|
+
#
|
36
|
+
# # POST and PUT with raw payloads
|
37
|
+
# RestClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
|
38
|
+
# RestClient.post 'http://example.com/resource.xml', xml_doc
|
39
|
+
# RestClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
|
40
|
+
#
|
41
|
+
# # DELETE
|
42
|
+
# RestClient.delete 'http://example.com/resource'
|
43
|
+
#
|
44
|
+
# # retreive the response http code and headers
|
45
|
+
# res = RestClient.get 'http://example.com/some.jpg'
|
46
|
+
# res.code # => 200
|
47
|
+
# res.headers[:content_type] # => 'image/jpg'
|
48
|
+
#
|
49
|
+
# # HEAD
|
50
|
+
# RestClient.head('http://example.com').headers
|
51
|
+
#
|
52
|
+
# To use with a proxy, just set RestClient.proxy to the proper http proxy:
|
53
|
+
#
|
54
|
+
# RestClient.proxy = "http://proxy.example.com/"
|
55
|
+
#
|
56
|
+
# Or inherit the proxy from the environment:
|
57
|
+
#
|
58
|
+
# RestClient.proxy = ENV['http_proxy']
|
59
|
+
#
|
60
|
+
# For live tests of RestClient, try using http://rest-test.heroku.com, which echoes back information about the rest call:
|
61
|
+
#
|
62
|
+
# >> RestClient.put 'http://rest-test.heroku.com/resource', :foo => 'baz'
|
63
|
+
# => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
|
64
|
+
#
|
65
|
+
module RestClient
|
66
|
+
|
67
|
+
def self.get(url, headers={}, &block)
|
68
|
+
Request.execute(:method => :get, :url => url, :headers => headers, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.post(url, payload, headers={}, &block)
|
72
|
+
Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers, &block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.put(url, payload, headers={}, &block)
|
76
|
+
Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.delete(url, headers={}, &block)
|
80
|
+
Request.execute(:method => :delete, :url => url, :headers => headers, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.head(url, headers={}, &block)
|
84
|
+
Request.execute(:method => :head, :url => url, :headers => headers, &block)
|
85
|
+
end
|
86
|
+
|
87
|
+
class << self
|
88
|
+
attr_accessor :proxy
|
89
|
+
end
|
90
|
+
|
91
|
+
# Setup the log for RestClient calls.
|
92
|
+
# Value should be a logger but can can be stdout, stderr, or a filename.
|
93
|
+
# You can also configure logging by the environment variable RESTCLIENT_LOG.
|
94
|
+
def self.log= log
|
95
|
+
@@log = create_log log
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.version
|
99
|
+
version_path = File.dirname(__FILE__) + "/../VERSION"
|
100
|
+
return File.read(version_path).chomp if File.file?(version_path)
|
101
|
+
"0.0.0"
|
102
|
+
end
|
103
|
+
|
104
|
+
# Create a log that respond to << like a logger
|
105
|
+
# param can be 'stdout', 'stderr', a string (then we will log to that file) or a logger (then we return it)
|
106
|
+
def self.create_log param
|
107
|
+
if param
|
108
|
+
if param.is_a? String
|
109
|
+
if param == 'stdout'
|
110
|
+
stdout_logger = Class.new do
|
111
|
+
def << obj
|
112
|
+
STDOUT.puts obj
|
113
|
+
end
|
114
|
+
end
|
115
|
+
stdout_logger.new
|
116
|
+
elsif param == 'stderr'
|
117
|
+
stderr_logger = Class.new do
|
118
|
+
def << obj
|
119
|
+
STDERR.puts obj
|
120
|
+
end
|
121
|
+
end
|
122
|
+
stderr_logger.new
|
123
|
+
else
|
124
|
+
file_logger = Class.new do
|
125
|
+
attr_writer :target_file
|
126
|
+
|
127
|
+
def << obj
|
128
|
+
File.open(@target_file, 'a') { |f| f.puts obj }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
logger = file_logger.new
|
132
|
+
logger.target_file = param
|
133
|
+
logger
|
134
|
+
end
|
135
|
+
else
|
136
|
+
param
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
@@env_log = create_log ENV['RESTCLIENT_LOG']
|
142
|
+
|
143
|
+
@@log = nil
|
144
|
+
|
145
|
+
def self.log # :nodoc:
|
146
|
+
@@env_log || @@log
|
147
|
+
end
|
148
|
+
|
149
|
+
@@before_execution_procs = []
|
150
|
+
|
151
|
+
# Add a Proc to be called before each request in executed.
|
152
|
+
# The proc parameters will be the http request and the request params.
|
153
|
+
def self.add_before_execution_proc &proc
|
154
|
+
@@before_execution_procs << proc
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.before_execution_procs # :nodoc:
|
158
|
+
@@before_execution_procs
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module RestClient
|
4
|
+
|
5
|
+
module AbstractResponse
|
6
|
+
|
7
|
+
attr_reader :net_http_res, :args
|
8
|
+
|
9
|
+
# HTTP status code
|
10
|
+
def code
|
11
|
+
@code ||= @net_http_res.code.to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
# A hash of the headers, beautified with symbols and underscores.
|
15
|
+
# e.g. "Content-type" will become :content_type.
|
16
|
+
def headers
|
17
|
+
@headers ||= AbstractResponse.beautify_headers(@net_http_res.to_hash)
|
18
|
+
end
|
19
|
+
|
20
|
+
# The raw headers.
|
21
|
+
def raw_headers
|
22
|
+
@raw_headers ||= @net_http_res.to_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
# Hash of cookies extracted from response headers
|
26
|
+
def cookies
|
27
|
+
@cookies ||= (self.headers[:set_cookie] || {}).inject({}) do |out, cookie_content|
|
28
|
+
CGI::Cookie::parse(cookie_content).each do |key, cookie|
|
29
|
+
unless ['expires', 'path'].include? key
|
30
|
+
out[CGI::escape(key)] = cookie.value[0] ? (CGI::escape(cookie.value[0]) || '') : ''
|
31
|
+
end
|
32
|
+
end
|
33
|
+
out
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return the default behavior corresponding to the response code:
|
38
|
+
# the response itself for code in 200..206, redirection for 301, 302 and 307 in get and head cases, redirection for 303 and an exception in other cases
|
39
|
+
def return! request = nil, &block
|
40
|
+
if (200..207).include? code
|
41
|
+
self
|
42
|
+
elsif [301, 302, 307].include? code
|
43
|
+
unless [:get, :head].include? args[:method]
|
44
|
+
raise Exceptions::EXCEPTIONS_MAP[code], self
|
45
|
+
else
|
46
|
+
follow_redirection(request, &block)
|
47
|
+
end
|
48
|
+
elsif code == 303
|
49
|
+
args[:method] = :get
|
50
|
+
args.delete :payload
|
51
|
+
follow_redirection(request, &block)
|
52
|
+
elsif Exceptions::EXCEPTIONS_MAP[code]
|
53
|
+
raise Exceptions::EXCEPTIONS_MAP[code], self
|
54
|
+
else
|
55
|
+
raise RequestFailed, self
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_i
|
60
|
+
code
|
61
|
+
end
|
62
|
+
|
63
|
+
def description
|
64
|
+
"#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Follow a redirection
|
68
|
+
def follow_redirection request = nil, &block
|
69
|
+
url = headers[:location]
|
70
|
+
if url !~ /^http/
|
71
|
+
url = URI.parse(args[:url]).merge(url).to_s
|
72
|
+
end
|
73
|
+
args[:url] = url
|
74
|
+
if request
|
75
|
+
args[:password] = request.password
|
76
|
+
args[:user] = request.user
|
77
|
+
args[:headers] = request.headers
|
78
|
+
end
|
79
|
+
Request.execute args, &block
|
80
|
+
end
|
81
|
+
|
82
|
+
def AbstractResponse.beautify_headers(headers)
|
83
|
+
headers.inject({}) do |out, (key, value)|
|
84
|
+
out[key.gsub(/-/, '_').downcase.to_sym] = %w{ set-cookie }.include?(key.downcase) ? value : value.first
|
85
|
+
out
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module RestClient
|
2
|
+
|
3
|
+
STATUSES = {100 => 'Continue',
|
4
|
+
101 => 'Switching Protocols',
|
5
|
+
102 => 'Processing', #WebDAV
|
6
|
+
|
7
|
+
200 => 'OK',
|
8
|
+
201 => 'Created',
|
9
|
+
202 => 'Accepted',
|
10
|
+
203 => 'Non-Authoritative Information', # http/1.1
|
11
|
+
204 => 'No Content',
|
12
|
+
205 => 'Reset Content',
|
13
|
+
206 => 'Partial Content',
|
14
|
+
207 => 'Multi-Status', #WebDAV
|
15
|
+
|
16
|
+
300 => 'Multiple Choices',
|
17
|
+
301 => 'Moved Permanently',
|
18
|
+
302 => 'Found',
|
19
|
+
303 => 'See Other', # http/1.1
|
20
|
+
304 => 'Not Modified',
|
21
|
+
305 => 'Use Proxy', # http/1.1
|
22
|
+
306 => 'Switch Proxy', # no longer used
|
23
|
+
307 => 'Temporary Redirect', # http/1.1
|
24
|
+
|
25
|
+
400 => 'Bad Request',
|
26
|
+
401 => 'Unauthorized',
|
27
|
+
402 => 'Payment Required',
|
28
|
+
403 => 'Forbidden',
|
29
|
+
404 => 'Resource Not Found',
|
30
|
+
405 => 'Method Not Allowed',
|
31
|
+
406 => 'Not Acceptable',
|
32
|
+
407 => 'Proxy Authentication Required',
|
33
|
+
408 => 'Request Timeout',
|
34
|
+
409 => 'Conflict',
|
35
|
+
410 => 'Gone',
|
36
|
+
411 => 'Length Required',
|
37
|
+
412 => 'Precondition Failed',
|
38
|
+
413 => 'Request Entity Too Large',
|
39
|
+
414 => 'Request-URI Too Long',
|
40
|
+
415 => 'Unsupported Media Type',
|
41
|
+
416 => 'Requested Range Not Satisfiable',
|
42
|
+
417 => 'Expectation Failed',
|
43
|
+
418 => 'I\'m A Teapot',
|
44
|
+
421 => 'Too Many Connections From This IP',
|
45
|
+
422 => 'Unprocessable Entity', #WebDAV
|
46
|
+
423 => 'Locked', #WebDAV
|
47
|
+
424 => 'Failed Dependency', #WebDAV
|
48
|
+
425 => 'Unordered Collection', #WebDAV
|
49
|
+
426 => 'Upgrade Required',
|
50
|
+
449 => 'Retry With', #Microsoft
|
51
|
+
450 => 'Blocked By Windows Parental Controls', #Microsoft
|
52
|
+
|
53
|
+
500 => 'Internal Server Error',
|
54
|
+
501 => 'Not Implemented',
|
55
|
+
502 => 'Bad Gateway',
|
56
|
+
503 => 'Service Unavailable',
|
57
|
+
504 => 'Gateway Timeout',
|
58
|
+
505 => 'HTTP Version Not Supported',
|
59
|
+
506 => 'Variant Also Negotiates',
|
60
|
+
507 => 'Insufficient Storage', #WebDAV
|
61
|
+
509 => 'Bandwidth Limit Exceeded', #Apache
|
62
|
+
510 => 'Not Extended'}
|
63
|
+
|
64
|
+
# Compatibility : make the Response act like a Net::HTTPResponse when needed
|
65
|
+
module ResponseForException
|
66
|
+
def method_missing symbol, *args
|
67
|
+
if net_http_res.respond_to? symbol
|
68
|
+
warn "[warning] The response contained in an RestClient::Exception is now a RestClient::Response instead of a Net::HTTPResponse, please update your code"
|
69
|
+
net_http_res.send symbol, *args
|
70
|
+
else
|
71
|
+
super
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# This is the base RestClient exception class. Rescue it if you want to
|
77
|
+
# catch any exception that your request might raise
|
78
|
+
# You can get the status code by e.http_code, or see anything about the
|
79
|
+
# response via e.response.
|
80
|
+
# For example, the entire result body (which is
|
81
|
+
# probably an HTML error page) is e.response.
|
82
|
+
class Exception < RuntimeError
|
83
|
+
attr_accessor :message, :response
|
84
|
+
|
85
|
+
def initialize response = nil
|
86
|
+
@response = response
|
87
|
+
|
88
|
+
# compatibility: this make the exception behave like a Net::HTTPResponse
|
89
|
+
response.extend ResponseForException if response
|
90
|
+
end
|
91
|
+
|
92
|
+
def http_code
|
93
|
+
# return integer for compatibility
|
94
|
+
@response.code.to_i if @response
|
95
|
+
end
|
96
|
+
|
97
|
+
def http_body
|
98
|
+
@response.body if @response
|
99
|
+
end
|
100
|
+
|
101
|
+
def inspect
|
102
|
+
"#{self.class} : #{http_code} #{message}"
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
# Compatibility
|
108
|
+
class ExceptionWithResponse < Exception
|
109
|
+
end
|
110
|
+
|
111
|
+
# The request failed with an error code not managed by the code
|
112
|
+
class RequestFailed < ExceptionWithResponse
|
113
|
+
|
114
|
+
def message
|
115
|
+
"HTTP status code #{http_code}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_s
|
119
|
+
message
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# We will a create an exception for each status code, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
124
|
+
module Exceptions
|
125
|
+
# Map http status codes to the corresponding exception class
|
126
|
+
EXCEPTIONS_MAP = {}
|
127
|
+
end
|
128
|
+
|
129
|
+
STATUSES.each_pair do |code, message|
|
130
|
+
|
131
|
+
# Compatibility
|
132
|
+
superclass = ([304, 401, 404].include? code) ? ExceptionWithResponse : RequestFailed
|
133
|
+
klass = Class.new(superclass) do
|
134
|
+
send(:define_method, :message) {message}
|
135
|
+
end
|
136
|
+
klass_constant = const_set message.delete(' \-\''), klass
|
137
|
+
Exceptions::EXCEPTIONS_MAP[code] = klass_constant
|
138
|
+
end
|
139
|
+
|
140
|
+
# A redirect was encountered; caught by execute to retry with the new url.
|
141
|
+
class Redirect < Exception
|
142
|
+
|
143
|
+
message = 'Redirect'
|
144
|
+
|
145
|
+
attr_accessor :url
|
146
|
+
|
147
|
+
def initialize(url)
|
148
|
+
@url = url
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# The server broke the connection prior to the request completing. Usually
|
153
|
+
# this means it crashed, or sometimes that your network connection was
|
154
|
+
# severed before it could complete.
|
155
|
+
class ServerBrokeConnection < Exception
|
156
|
+
message = 'Server broke connection'
|
157
|
+
end
|
158
|
+
|
159
|
+
class SSLCertificateNotVerified < Exception
|
160
|
+
def initialize(message)
|
161
|
+
super(nil)
|
162
|
+
self.message = message
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# backwards compatibility
|
168
|
+
class RestClient::Request
|
169
|
+
Redirect = RestClient::Redirect
|
170
|
+
Unauthorized = RestClient::Unauthorized
|
171
|
+
RequestFailed = RestClient::RequestFailed
|
172
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# Replace the request method in Net::HTTP to sniff the body type
|
3
|
+
# and set the stream if appropriate
|
4
|
+
#
|
5
|
+
# Taken from:
|
6
|
+
# http://www.missiondata.com/blog/ruby/29/streaming-data-to-s3-with-ruby/
|
7
|
+
|
8
|
+
module Net
|
9
|
+
class HTTP
|
10
|
+
alias __request__ request
|
11
|
+
|
12
|
+
def request(req, body=nil, &block)
|
13
|
+
if body != nil && body.respond_to?(:read)
|
14
|
+
req.body_stream = body
|
15
|
+
return __request__(req, nil, &block)
|
16
|
+
else
|
17
|
+
return __request__(req, body, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|