httparty 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of httparty might be problematic. Click here for more details.
- data/History +18 -0
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/examples/aaws.rb +2 -2
- data/features/digest_authentication.feature +20 -0
- data/features/handles_compressed_responses.feature +19 -0
- data/features/steps/env.rb +9 -2
- data/features/steps/httparty_steps.rb +8 -0
- data/features/steps/mongrel_helper.rb +72 -35
- data/features/steps/remote_service_steps.rb +22 -5
- data/features/supports_timeout_option.feature +1 -0
- data/httparty.gemspec +15 -12
- data/lib/httparty.rb +47 -6
- data/lib/httparty/net_digest_auth.rb +35 -0
- data/lib/httparty/request.rb +121 -100
- data/lib/httparty/response.rb +55 -10
- data/spec/httparty/request_spec.rb +65 -2
- data/spec/httparty/response_spec.rb +53 -32
- data/spec/httparty_spec.rb +31 -1
- data/spec/spec_helper.rb +6 -2
- data/spec/support/stub_response.rb +1 -1
- metadata +62 -33
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Net
|
5
|
+
module HTTPHeader
|
6
|
+
def digest_auth(user, password, response)
|
7
|
+
response['www-authenticate'] =~ /^(\w+) (.*)/
|
8
|
+
|
9
|
+
params = {}
|
10
|
+
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
|
11
|
+
params.merge!("cnonce" => Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))))
|
12
|
+
|
13
|
+
a_1 = Digest::MD5.hexdigest("#{user}:#{params['realm']}:#{password}")
|
14
|
+
a_2 = Digest::MD5.hexdigest("#{@method}:#{@path}")
|
15
|
+
|
16
|
+
request_digest = Digest::MD5.hexdigest(
|
17
|
+
[a_1, params['nonce'], "0", params['cnonce'], params['qop'], a_2].join(":")
|
18
|
+
)
|
19
|
+
|
20
|
+
header = [
|
21
|
+
%Q(Digest username="#{user}"),
|
22
|
+
%Q(realm="#{params['realm']}"),
|
23
|
+
%Q(qop="#{params['qop']}"),
|
24
|
+
%Q(uri="#{@path}"),
|
25
|
+
%Q(nonce="#{params['nonce']}"),
|
26
|
+
%Q(nc="0"),
|
27
|
+
%Q(cnonce="#{params['cnonce']}"),
|
28
|
+
%Q(opaque="#{params['opaque']}"),
|
29
|
+
%Q(response="#{request_digest}")
|
30
|
+
]
|
31
|
+
|
32
|
+
@header['Authorization'] = header
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/httparty/request.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'uri'
|
2
|
-
|
3
1
|
module HTTParty
|
4
2
|
class Request #:nodoc:
|
5
3
|
SupportedHTTPMethods = [
|
@@ -13,7 +11,7 @@ module HTTParty
|
|
13
11
|
|
14
12
|
SupportedURISchemes = [URI::HTTP, URI::HTTPS]
|
15
13
|
|
16
|
-
attr_accessor :http_method, :path, :options, :last_response
|
14
|
+
attr_accessor :http_method, :path, :options, :last_response, :redirect
|
17
15
|
|
18
16
|
def initialize(http_method, path, o={})
|
19
17
|
self.http_method = http_method
|
@@ -33,7 +31,7 @@ module HTTParty
|
|
33
31
|
new_uri = path.relative? ? URI.parse("#{options[:base_uri]}#{path}") : path
|
34
32
|
|
35
33
|
# avoid double query string on redirects [#12]
|
36
|
-
unless
|
34
|
+
unless redirect
|
37
35
|
new_uri.query = query_string(new_uri)
|
38
36
|
end
|
39
37
|
|
@@ -45,14 +43,13 @@ module HTTParty
|
|
45
43
|
end
|
46
44
|
|
47
45
|
def format
|
48
|
-
options[:format]
|
46
|
+
options[:format] || (format_from_mimetype(last_response['content-type']) if last_response)
|
49
47
|
end
|
50
48
|
|
51
49
|
def parser
|
52
50
|
options[:parser]
|
53
51
|
end
|
54
52
|
|
55
|
-
|
56
53
|
def perform
|
57
54
|
validate
|
58
55
|
setup_raw_request
|
@@ -62,133 +59,157 @@ module HTTParty
|
|
62
59
|
|
63
60
|
private
|
64
61
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
if options[:timeout] && options[:timeout].is_a?(Integer)
|
70
|
-
http.open_timeout = options[:timeout]
|
71
|
-
http.read_timeout = options[:timeout]
|
72
|
-
end
|
62
|
+
def http
|
63
|
+
http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
|
64
|
+
http.use_ssl = ssl_implied?
|
73
65
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
78
|
-
else
|
79
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
80
|
-
end
|
81
|
-
|
82
|
-
if options[:debug_output]
|
83
|
-
http.set_debug_output(options[:debug_output])
|
84
|
-
end
|
85
|
-
|
86
|
-
http
|
66
|
+
if options[:timeout] && options[:timeout].is_a?(Integer)
|
67
|
+
http.open_timeout = options[:timeout]
|
68
|
+
http.read_timeout = options[:timeout]
|
87
69
|
end
|
88
70
|
|
89
|
-
|
90
|
-
|
71
|
+
if options[:pem] && http.use_ssl?
|
72
|
+
http.cert = OpenSSL::X509::Certificate.new(options[:pem])
|
73
|
+
http.key = OpenSSL::PKey::RSA.new(options[:pem])
|
74
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
75
|
+
else
|
76
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
91
77
|
end
|
92
78
|
|
93
|
-
|
94
|
-
|
79
|
+
if options[:debug_output]
|
80
|
+
http.set_debug_output(options[:debug_output])
|
95
81
|
end
|
96
82
|
|
97
|
-
|
98
|
-
|
99
|
-
end
|
83
|
+
http
|
84
|
+
end
|
100
85
|
|
101
|
-
|
102
|
-
|
103
|
-
|
86
|
+
def ssl_implied?
|
87
|
+
uri.port == 443 || uri.instance_of?(URI::HTTPS)
|
88
|
+
end
|
104
89
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
@raw_request.initialize_http_header(options[:headers])
|
109
|
-
@raw_request.basic_auth(username, password) if options[:basic_auth]
|
110
|
-
end
|
90
|
+
def body
|
91
|
+
options[:body].is_a?(Hash) ? options[:body].to_params : options[:body]
|
92
|
+
end
|
111
93
|
|
112
|
-
|
113
|
-
|
114
|
-
|
94
|
+
def credentials
|
95
|
+
options[:basic_auth] || options[:digest_auth]
|
96
|
+
end
|
97
|
+
|
98
|
+
def username
|
99
|
+
credentials[:username]
|
100
|
+
end
|
101
|
+
|
102
|
+
def password
|
103
|
+
credentials[:password]
|
104
|
+
end
|
115
105
|
|
116
|
-
|
117
|
-
|
118
|
-
|
106
|
+
def setup_raw_request
|
107
|
+
@raw_request = http_method.new(uri.request_uri)
|
108
|
+
@raw_request.body = body if body
|
109
|
+
@raw_request.initialize_http_header(options[:headers])
|
110
|
+
@raw_request.basic_auth(username, password) if options[:basic_auth]
|
111
|
+
setup_digest_auth if options[:digest_auth]
|
112
|
+
end
|
113
|
+
|
114
|
+
def setup_digest_auth
|
115
|
+
res = http.head(uri.request_uri, options[:headers])
|
116
|
+
if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
|
117
|
+
@raw_request.digest_auth(username, password, res)
|
119
118
|
end
|
119
|
+
end
|
120
120
|
|
121
|
-
|
122
|
-
|
123
|
-
|
121
|
+
def perform_actual_request
|
122
|
+
http.request(@raw_request)
|
123
|
+
end
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
125
|
+
def get_response
|
126
|
+
self.last_response = perform_actual_request
|
127
|
+
end
|
128
|
+
|
129
|
+
def query_string(uri)
|
130
|
+
query_string_parts = []
|
131
|
+
query_string_parts << uri.query unless uri.query.nil?
|
131
132
|
|
132
|
-
|
133
|
+
if options[:query].is_a?(Hash)
|
134
|
+
query_string_parts << options[:default_params].merge(options[:query]).to_params
|
135
|
+
else
|
136
|
+
query_string_parts << options[:default_params].to_params unless options[:default_params].empty?
|
137
|
+
query_string_parts << options[:query] unless options[:query].nil?
|
133
138
|
end
|
134
139
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
140
|
+
query_string_parts.size > 0 ? query_string_parts.join('&') : nil
|
141
|
+
end
|
142
|
+
|
143
|
+
# Raises exception Net::XXX (http error code) if an http error occured
|
144
|
+
def handle_response
|
145
|
+
handle_deflation
|
146
|
+
case last_response
|
147
|
+
when Net::HTTPMultipleChoice, # 300
|
148
|
+
Net::HTTPMovedPermanently, # 301
|
149
|
+
Net::HTTPFound, # 302
|
150
|
+
Net::HTTPSeeOther, # 303
|
151
|
+
Net::HTTPUseProxy, # 305
|
152
|
+
Net::HTTPTemporaryRedirect
|
153
|
+
if last_response.key?('location')
|
154
|
+
options[:limit] -= 1
|
155
|
+
self.path = last_response['location']
|
156
|
+
self.redirect = true
|
157
|
+
self.http_method = Net::HTTP::Get unless options[:maintain_method_across_redirects]
|
158
|
+
capture_cookies(last_response)
|
159
|
+
perform
|
154
160
|
else
|
155
|
-
|
161
|
+
last_response
|
156
162
|
end
|
163
|
+
else
|
164
|
+
Response.new(last_response, parse_response(last_response.body))
|
157
165
|
end
|
166
|
+
end
|
158
167
|
|
159
|
-
|
160
|
-
|
168
|
+
# Inspired by Ruby 1.9
|
169
|
+
def handle_deflation
|
170
|
+
case last_response["content-encoding"]
|
171
|
+
when "gzip"
|
172
|
+
body_io = StringIO.new(last_response.body)
|
173
|
+
last_response.body.replace Zlib::GzipReader.new(body_io).read
|
174
|
+
when "deflate"
|
175
|
+
last_response.body.replace Zlib::Inflate.inflate(last_response.body)
|
161
176
|
end
|
177
|
+
end
|
162
178
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
|
167
|
-
cookies_hash.add_cookies(response['Set-Cookie'])
|
168
|
-
options[:headers] ||= {}
|
169
|
-
options[:headers]['Cookie'] = cookies_hash.to_cookie_string
|
170
|
-
end
|
179
|
+
def parse_response(body)
|
180
|
+
parser.call(body, format)
|
181
|
+
end
|
171
182
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
183
|
+
def capture_cookies(response)
|
184
|
+
return unless response['Set-Cookie']
|
185
|
+
cookies_hash = HTTParty::CookieHash.new()
|
186
|
+
cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
|
187
|
+
cookies_hash.add_cookies(response['Set-Cookie'])
|
188
|
+
options[:headers] ||= {}
|
189
|
+
options[:headers]['Cookie'] = cookies_hash.to_cookie_string
|
190
|
+
end
|
191
|
+
|
192
|
+
# Uses the HTTP Content-Type header to determine the format of the
|
193
|
+
# response It compares the MIME type returned to the types stored in the
|
194
|
+
# SupportedFormats hash
|
195
|
+
def format_from_mimetype(mimetype)
|
196
|
+
if mimetype && parser.respond_to?(:format_from_mimetype)
|
197
|
+
parser.format_from_mimetype(mimetype)
|
179
198
|
end
|
199
|
+
end
|
180
200
|
|
181
201
|
def validate
|
182
202
|
raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
|
183
203
|
raise ArgumentError, 'only get, post, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
|
184
204
|
raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
|
205
|
+
raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]
|
185
206
|
raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
|
207
|
+
raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].is_a?(Hash)
|
186
208
|
raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].is_a?(Hash)
|
187
209
|
end
|
188
210
|
|
189
|
-
|
190
|
-
|
191
|
-
|
211
|
+
def post?
|
212
|
+
Net::HTTP::Post == http_method
|
213
|
+
end
|
192
214
|
end
|
193
215
|
end
|
194
|
-
|
data/lib/httparty/response.rb
CHANGED
@@ -1,18 +1,63 @@
|
|
1
1
|
module HTTParty
|
2
2
|
class Response < HTTParty::BasicObject #:nodoc:
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
class Headers
|
4
|
+
include Net::HTTPHeader
|
5
|
+
|
6
|
+
def initialize(header)
|
7
|
+
@header = header
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
@header == other
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
@header.inspect
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(name, *args, &block)
|
19
|
+
if @header.respond_to?(name)
|
20
|
+
@header.send(name, *args, &block)
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to?(method)
|
27
|
+
super || @header.respond_to?(method)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :response, :parsed_response, :body, :headers
|
32
|
+
|
33
|
+
def initialize(response, parsed_response)
|
34
|
+
@response = response
|
35
|
+
@body = response.body
|
36
|
+
@parsed_response = parsed_response
|
37
|
+
@headers = Headers.new(response.to_hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
def class
|
41
|
+
Response
|
42
|
+
end
|
43
|
+
|
44
|
+
def code
|
45
|
+
response.code.to_i
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect
|
49
|
+
inspect_id = "%x" % (object_id * 2)
|
50
|
+
%(#<#{self.class}:0x#{inspect_id} @parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>)
|
12
51
|
end
|
13
52
|
|
14
53
|
def method_missing(name, *args, &block)
|
15
|
-
|
54
|
+
if parsed_response.respond_to?(name)
|
55
|
+
parsed_response.send(name, *args, &block)
|
56
|
+
elsif response.respond_to?(name)
|
57
|
+
response.send(name, *args, &block)
|
58
|
+
else
|
59
|
+
super
|
60
|
+
end
|
16
61
|
end
|
17
62
|
end
|
18
63
|
end
|
@@ -19,9 +19,34 @@ describe HTTParty::Request do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
describe "#format" do
|
22
|
-
|
23
|
-
|
22
|
+
context "request yet to be made" do
|
23
|
+
it "returns format option" do
|
24
|
+
request = HTTParty::Request.new 'get', '/', :format => :xml
|
25
|
+
request.format.should == :xml
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns nil format" do
|
29
|
+
request = HTTParty::Request.new 'get', '/'
|
30
|
+
request.format.should be_nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "request has been made" do
|
35
|
+
it "returns format option" do
|
36
|
+
request = HTTParty::Request.new 'get', '/', :format => :xml
|
37
|
+
request.last_response = stub
|
38
|
+
request.format.should == :xml
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns the content-type from the last response when the option is not set" do
|
42
|
+
request = HTTParty::Request.new 'get', '/'
|
43
|
+
response = stub
|
44
|
+
response.should_receive(:[]).with('content-type').and_return('text/json')
|
45
|
+
request.last_response = response
|
46
|
+
request.format.should == :json
|
47
|
+
end
|
24
48
|
end
|
49
|
+
|
25
50
|
end
|
26
51
|
|
27
52
|
context "options" do
|
@@ -30,6 +55,17 @@ describe HTTParty::Request do
|
|
30
55
|
@request.send(:setup_raw_request)
|
31
56
|
@request.instance_variable_get(:@raw_request)['authorization'].should_not be_nil
|
32
57
|
end
|
58
|
+
|
59
|
+
it "should use digest auth when configured" do
|
60
|
+
FakeWeb.register_uri(:head, "http://api.foo.com/v1",
|
61
|
+
:www_authenticate => 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false')
|
62
|
+
|
63
|
+
@request.options[:digest_auth] = {:username => 'foobar', :password => 'secret'}
|
64
|
+
@request.send(:setup_raw_request)
|
65
|
+
|
66
|
+
raw_request = @request.instance_variable_get(:@raw_request)
|
67
|
+
raw_request.instance_variable_get(:@header)['Authorization'].should_not be_nil
|
68
|
+
end
|
33
69
|
end
|
34
70
|
|
35
71
|
describe "#uri" do
|
@@ -353,6 +389,13 @@ describe HTTParty::Request do
|
|
353
389
|
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
354
390
|
@request.http_method.should == Net::HTTP::Get
|
355
391
|
end
|
392
|
+
|
393
|
+
it 'should not make resulting request a get request if options[:maintain_method_across_redirects] is true' do
|
394
|
+
@request.options[:maintain_method_across_redirects] = true
|
395
|
+
@request.http_method = Net::HTTP::Delete
|
396
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
397
|
+
@request.http_method.should == Net::HTTP::Delete
|
398
|
+
end
|
356
399
|
end
|
357
400
|
|
358
401
|
describe "infinitely" do
|
@@ -373,5 +416,25 @@ describe HTTParty::Request do
|
|
373
416
|
}.should raise_error(ArgumentError)
|
374
417
|
end
|
375
418
|
end
|
419
|
+
|
420
|
+
describe "argument validation" do
|
421
|
+
it "should raise argument error if basic_auth and digest_auth are both present" do
|
422
|
+
lambda {
|
423
|
+
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => {}, :digest_auth => {}).perform
|
424
|
+
}.should raise_error(ArgumentError, "only one authentication method, :basic_auth or :digest_auth may be used at a time")
|
425
|
+
end
|
426
|
+
|
427
|
+
it "should raise argument error if basic_auth is not a hash" do
|
428
|
+
lambda {
|
429
|
+
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => ["foo", "bar"]).perform
|
430
|
+
}.should raise_error(ArgumentError, ":basic_auth must be a hash")
|
431
|
+
end
|
432
|
+
|
433
|
+
it "should raise argument error if digest_auth is not a hash" do
|
434
|
+
lambda {
|
435
|
+
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :digest_auth => ["foo", "bar"]).perform
|
436
|
+
}.should raise_error(ArgumentError, ":digest_auth must be a hash")
|
437
|
+
end
|
438
|
+
end
|
376
439
|
end
|
377
440
|
|