jugend-httparty 0.5.2.3 → 0.5.3.4

Sign up to get free protection for your applications and to get access to all the features.
data/History CHANGED
@@ -1,12 +1,30 @@
1
- == 0.5.2.3
2
- * Repackage gem, old gem contains master branch files
1
+ == 0.5.3.4
2
+ * Update gem name to jugend-httparty
3
+
4
+ == 0.5.3.3
5
+ * Remove RubyforgeTasks
6
+
7
+ == 0.5.3.2
8
+ * Remove deprecated rubyforge gem push
9
+
10
+ == 0.5.3.1
11
+ * Update Pathname initialization to support earlier version of ruby (<=1.8.4)
3
12
 
4
- == 0.5.2.2
5
- * Update gem package to include the right version of httparty.rb
13
+ == 0.5.3 (master)
14
+ * major enhancements
15
+ * Digest Auth (bartiaco, sbecker, gilles, and aaronrussell)
16
+ * Maintain HTTP method across redirects (bartiaco and sbecker)
17
+ * HTTParty::Response#response returns the Net::HTTPResponse object
18
+ * HTTParty::Response#headers returns a HTTParty::Response::Headers object
19
+ which quacks like a Hash + Net::HTTPHeader. The #headers method continues
20
+ to be backwards-compatible with the old Hash return value but may become
21
+ deprecated in the future.
6
22
 
7
- == 0.5.2.1
8
- * Update Pathname initialization to support earlier version of Ruby (1.8.4)
9
- * Remove references to rubyforge
23
+ * minor enhancements
24
+ * Update crack requirement to version 0.1.7
25
+ You may still get a warning because Crack's version constant is out of date
26
+ * Timeout option can be set for all requests using HTTParty.default_timeout (taazza)
27
+ * Closed #38 "headers hash should downcase keys so canonical header name can be used"
10
28
 
11
29
  == 0.5.2 2010-01-31
12
30
  * minor enhancements
data/Rakefile CHANGED
@@ -7,14 +7,14 @@ begin
7
7
  gem.summary = %Q{Makes http fun! Also, makes consuming restful web services dead easy.}
8
8
  gem.description = %Q{Makes http fun! Also, makes consuming restful web services dead easy.}
9
9
  gem.email = "nunemaker@gmail.com"
10
- gem.homepage = "http://httparty.rubyforge.org"
10
+ gem.homepage = "http://github.com/jugend/httparty"
11
11
  gem.authors = ["John Nunemaker", "Sandro Turriate"]
12
- gem.add_dependency 'crack', '0.1.6'
12
+ gem.add_dependency 'crack', '0.1.7'
13
13
  gem.add_development_dependency "activesupport", "~>2.3"
14
- gem.add_development_dependency "cucumber", "~>0.4"
14
+ gem.add_development_dependency "cucumber", "~>0.7"
15
15
  gem.add_development_dependency "fakeweb", "~>1.2"
16
16
  gem.add_development_dependency "mongrel", "~>1.1"
17
- gem.add_development_dependency "rspec", "1.2.9"
17
+ gem.add_development_dependency "rspec", "~>1.3"
18
18
  gem.post_install_message = "When you HTTParty, you must party hard!"
19
19
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
20
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.2.3
1
+ 0.5.3.4
@@ -0,0 +1,20 @@
1
+ Feature: Digest Authentication
2
+
3
+ As a developer
4
+ I want to be able to use a service that requires Digest Authentication
5
+ Because that is not an uncommon requirement
6
+
7
+ Scenario: Passing no credentials to a page requiring Digest Authentication
8
+ Given a restricted page at '/protected.html'
9
+ When I call HTTParty#get with '/protected.html'
10
+ Then it should return a response with a 401 response code
11
+
12
+ Scenario: Passing proper credentials to a page requiring Digest Authentication
13
+ Given a remote service that returns 'Digest Authenticated Page'
14
+ And that service is accessed at the path '/protected.html'
15
+ And that service is protected by Digest Authentication
16
+ And that service requires the username 'jcash' with the password 'maninblack'
17
+ When I call HTTParty#get with '/protected.html' and a digest_auth hash:
18
+ | username | password |
19
+ | jcash | maninblack |
20
+ Then the return value should match 'Digest Authenticated Page'
@@ -4,7 +4,14 @@ require 'lib/httparty'
4
4
  require 'spec/expectations'
5
5
 
6
6
  Before do
7
- port = ENV["HTTPARTY_PORT"] || 31981
7
+ def new_port
8
+ server = TCPServer.new('0.0.0.0', nil)
9
+ port = server.addr[1]
10
+ ensure
11
+ server.close
12
+ end
13
+
14
+ port = ENV["HTTPARTY_PORT"] || new_port
8
15
  @host_and_port = "0.0.0.0:#{port}"
9
16
  @server = Mongrel::HttpServer.new("0.0.0.0", port)
10
17
  @server.run
@@ -17,3 +17,11 @@ When /I call HTTParty#get with '(.*)' and a basic_auth hash:/ do |url, auth_tabl
17
17
  :basic_auth => { :username => h["username"], :password => h["password"] }
18
18
  )
19
19
  end
20
+
21
+ When /I call HTTParty#get with '(.*)' and a digest_auth hash:/ do |url, auth_table|
22
+ h = auth_table.hashes.first
23
+ @response_from_httparty = HTTParty.get(
24
+ "http://#{@host_and_port}#{url}",
25
+ :digest_auth => { :username => h["username"], :password => h["password"] }
26
+ )
27
+ end
@@ -50,6 +50,28 @@ def add_basic_authentication_to(handler)
50
50
  handler.extend(m)
51
51
  end
52
52
 
53
+ def add_digest_authentication_to(handler)
54
+ m = Module.new do
55
+ attr_writer :username, :password
56
+
57
+ def self.extended(base)
58
+ base.instance_eval { @custom_headers["WWW-Authenticate"] = 'Digest realm="testrealm@host.com",qop="auth,auth-int",nonce="nonce",opaque="opaque"' }
59
+ base.class_eval { alias_method_chain :process, :digest_authentication }
60
+ end
61
+
62
+ def process_with_digest_authentication(request, response)
63
+ if authorized?(request) then process_without_digest_authentication(request, response)
64
+ else reply_with(response, 401, "Incorrect. You have 20 seconds to comply.")
65
+ end
66
+ end
67
+
68
+ def authorized?(request)
69
+ request.params["HTTP_AUTHORIZATION"] =~ /Digest.*uri=/
70
+ end
71
+ end
72
+ handler.extend(m)
73
+ end
74
+
53
75
  def new_mongrel_redirector(target_url, relative_path = false)
54
76
  target_url = "http://#{@host_and_port}#{target_url}" unless relative_path
55
77
  Mongrel::RedirectHandler.new(target_url)
@@ -13,8 +13,8 @@ Given /that service is accessed at the path '(.*)'/ do |path|
13
13
  end
14
14
 
15
15
  Given /^that service takes (\d+) seconds to generate a response$/ do |time|
16
- preprocessor = lambda { sleep time.to_i }
17
- @handler.preprocessor = preprocessor
16
+ @server_response_time = time.to_i
17
+ @handler.preprocessor = lambda { sleep time.to_i }
18
18
  end
19
19
 
20
20
  Given /the response from the service has a Content-Type of '(.*)'/ do |content_type|
@@ -33,6 +33,10 @@ Given /that service is protected by Basic Authentication/ do
33
33
  add_basic_authentication_to @handler
34
34
  end
35
35
 
36
+ Given /that service is protected by Digest Authentication/ do
37
+ add_digest_authentication_to @handler
38
+ end
39
+
36
40
  Given /that service requires the username '(.*)' with the password '(.*)'/ do |username, password|
37
41
  @handler.username = username
38
42
  @handler.password = password
@@ -50,3 +54,8 @@ end
50
54
  Given /I want to hit this in a browser/ do
51
55
  @server.acceptor.join
52
56
  end
57
+
58
+ Then /I wait for the server to recover/ do
59
+ timeout = @request_options[:timeout] || 0
60
+ sleep @server_response_time - timeout
61
+ end
@@ -10,3 +10,4 @@ Feature: Supports the timeout option
10
10
  When I set my HTTParty timeout option to 1
11
11
  And I call HTTParty#get with '/service.html'
12
12
  Then it should raise a Timeout::Error exception
13
+ And I wait for the server to recover
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{jugend-httparty}
8
- s.version = "0.5.2.3"
8
+ s.version = "0.5.3.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["John Nunemaker", "Sandro Turriate"]
12
- s.date = %q{2010-06-11}
12
+ s.date = %q{2010-05-26}
13
13
  s.default_executable = %q{httparty}
14
14
  s.description = %q{Makes http fun! Also, makes consuming restful web services dead easy.}
15
15
  s.email = %q{nunemaker@gmail.com}
@@ -38,6 +38,7 @@ Gem::Specification.new do |s|
38
38
  "features/basic_authentication.feature",
39
39
  "features/command_line.feature",
40
40
  "features/deals_with_http_error_codes.feature",
41
+ "features/digest_authentication.feature",
41
42
  "features/handles_multiple_formats.feature",
42
43
  "features/steps/env.rb",
43
44
  "features/steps/httparty_response_steps.rb",
@@ -52,6 +53,7 @@ Gem::Specification.new do |s|
52
53
  "lib/httparty/core_extensions.rb",
53
54
  "lib/httparty/exceptions.rb",
54
55
  "lib/httparty/module_inheritable_attributes.rb",
56
+ "lib/httparty/net_digest_auth.rb",
55
57
  "lib/httparty/parser.rb",
56
58
  "lib/httparty/request.rb",
57
59
  "lib/httparty/response.rb",
@@ -72,7 +74,7 @@ Gem::Specification.new do |s|
72
74
  "website/css/common.css",
73
75
  "website/index.html"
74
76
  ]
75
- s.homepage = %q{http://httparty.rubyforge.org}
77
+ s.homepage = %q{http://github.com/jugend/httparty}
76
78
  s.post_install_message = %q{When you HTTParty, you must party hard!}
77
79
  s.rdoc_options = ["--charset=UTF-8"]
78
80
  s.require_paths = ["lib"]
@@ -101,27 +103,27 @@ Gem::Specification.new do |s|
101
103
  s.specification_version = 3
102
104
 
103
105
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
104
- s.add_runtime_dependency(%q<crack>, ["= 0.1.6"])
106
+ s.add_runtime_dependency(%q<crack>, ["= 0.1.7"])
105
107
  s.add_development_dependency(%q<activesupport>, ["~> 2.3"])
106
- s.add_development_dependency(%q<cucumber>, ["~> 0.4"])
108
+ s.add_development_dependency(%q<cucumber>, ["~> 0.7"])
107
109
  s.add_development_dependency(%q<fakeweb>, ["~> 1.2"])
108
110
  s.add_development_dependency(%q<mongrel>, ["~> 1.1"])
109
- s.add_development_dependency(%q<rspec>, ["= 1.2.9"])
111
+ s.add_development_dependency(%q<rspec>, ["~> 1.3"])
110
112
  else
111
- s.add_dependency(%q<crack>, ["= 0.1.6"])
113
+ s.add_dependency(%q<crack>, ["= 0.1.7"])
112
114
  s.add_dependency(%q<activesupport>, ["~> 2.3"])
113
- s.add_dependency(%q<cucumber>, ["~> 0.4"])
115
+ s.add_dependency(%q<cucumber>, ["~> 0.7"])
114
116
  s.add_dependency(%q<fakeweb>, ["~> 1.2"])
115
117
  s.add_dependency(%q<mongrel>, ["~> 1.1"])
116
- s.add_dependency(%q<rspec>, ["= 1.2.9"])
118
+ s.add_dependency(%q<rspec>, ["~> 1.3"])
117
119
  end
118
120
  else
119
- s.add_dependency(%q<crack>, ["= 0.1.6"])
121
+ s.add_dependency(%q<crack>, ["= 0.1.7"])
120
122
  s.add_dependency(%q<activesupport>, ["~> 2.3"])
121
- s.add_dependency(%q<cucumber>, ["~> 0.4"])
123
+ s.add_dependency(%q<cucumber>, ["~> 0.7"])
122
124
  s.add_dependency(%q<fakeweb>, ["~> 1.2"])
123
125
  s.add_dependency(%q<mongrel>, ["~> 1.1"])
124
- s.add_dependency(%q<rspec>, ["= 1.2.9"])
126
+ s.add_dependency(%q<rspec>, ["~> 1.3"])
125
127
  end
126
128
  end
127
129
 
@@ -3,17 +3,15 @@ require 'net/http'
3
3
  require 'net/https'
4
4
  require 'crack'
5
5
 
6
- if Crack::VERSION != "0.1.6"
7
- warn "warning: HTTParty depends on version 0.1.6 of crack, not #{Crack::VERSION}."
8
- end
9
-
10
- dir = Pathname(__FILE__).dirname.expand_path
6
+ dir = Pathname.new(__FILE__).dirname.expand_path
11
7
 
12
8
  require dir + 'httparty/module_inheritable_attributes'
13
9
  require dir + 'httparty/cookie_hash'
10
+ require dir + 'httparty/net_digest_auth'
14
11
 
15
12
  module HTTParty
16
13
  VERSION = "0.5.2".freeze
14
+ CRACK_DEPENDENCY = "0.1.7".freeze
17
15
 
18
16
  module AllowedFormatsDeprecation
19
17
  def const_missing(const)
@@ -73,6 +71,16 @@ module HTTParty
73
71
  default_options[:basic_auth] = {:username => u, :password => p}
74
72
  end
75
73
 
74
+ # Allows setting digest authentication username and password.
75
+ #
76
+ # class Foo
77
+ # include HTTParty
78
+ # digest_auth 'username', 'password'
79
+ # end
80
+ def digest_auth(u, p)
81
+ default_options[:digest_auth] = {:username => u, :password => p}
82
+ end
83
+
76
84
  # Allows setting default parameters to be appended to each request.
77
85
  # Great for api keys and such.
78
86
  #
@@ -86,6 +94,18 @@ module HTTParty
86
94
  default_options[:default_params].merge!(h)
87
95
  end
88
96
 
97
+ # Allows setting a default timeout for all HTTP calls
98
+ # Timeout is specified in seconds.
99
+ #
100
+ # class Foo
101
+ # include HTTParty
102
+ # default_timeout 10
103
+ # end
104
+ def default_timeout(t)
105
+ raise ArgumentError, 'Timeout must be an integer' unless t && t.is_a?(Integer)
106
+ default_options[:timeout] = t
107
+ end
108
+
89
109
  # Set an output stream for debugging, defaults to $stderr.
90
110
  # The output stream is passed on to Net::HTTP#set_debug_output.
91
111
  #
@@ -97,7 +117,7 @@ module HTTParty
97
117
  default_options[:debug_output] = stream
98
118
  end
99
119
 
100
- # Allows setting a base uri to be used for each request.
120
+ # Allows setting HTTP headers to be used for each request.
101
121
  #
102
122
  # class Foo
103
123
  # include HTTParty
@@ -154,6 +174,21 @@ module HTTParty
154
174
  default_options[:no_follow] = value
155
175
  end
156
176
 
177
+ # Declare that you wish to maintain the chosen HTTP method across redirects.
178
+ # The default behavior is to follow redirects via the GET method.
179
+ # If you wish to maintain the original method, you can set this option to true.
180
+ #
181
+ # @example
182
+ # class Foo
183
+ # include HTTParty
184
+ # base_uri 'http://google.com'
185
+ # maintain_method_across_redirects true
186
+ # end
187
+
188
+ def maintain_method_across_redirects(value = true)
189
+ default_options[:maintain_method_across_redirects] = value
190
+ end
191
+
157
192
  # Allows setting a PEM file to be used
158
193
  #
159
194
  # class Foo
@@ -302,3 +337,7 @@ require dir + 'httparty/exceptions'
302
337
  require dir + 'httparty/parser'
303
338
  require dir + 'httparty/request'
304
339
  require dir + 'httparty/response'
340
+
341
+ if Crack::VERSION != HTTParty::CRACK_DEPENDENCY
342
+ warn "warning: HTTParty depends on version #{HTTParty::CRACK_DEPENDENCY} of crack, not #{Crack::VERSION}."
343
+ end
@@ -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
@@ -13,7 +13,7 @@ module HTTParty
13
13
 
14
14
  SupportedURISchemes = [URI::HTTP, URI::HTTPS]
15
15
 
16
- attr_accessor :http_method, :path, :options, :last_response
16
+ attr_accessor :http_method, :path, :options, :last_response, :redirect
17
17
 
18
18
  def initialize(http_method, path, o={})
19
19
  self.http_method = http_method
@@ -33,7 +33,7 @@ module HTTParty
33
33
  new_uri = path.relative? ? URI.parse("#{options[:base_uri]}#{path}") : path
34
34
 
35
35
  # avoid double query string on redirects [#12]
36
- unless @redirect
36
+ unless redirect
37
37
  new_uri.query = query_string(new_uri)
38
38
  end
39
39
 
@@ -52,7 +52,6 @@ module HTTParty
52
52
  options[:parser]
53
53
  end
54
54
 
55
-
56
55
  def perform
57
56
  validate
58
57
  setup_raw_request
@@ -62,133 +61,146 @@ module HTTParty
62
61
 
63
62
  private
64
63
 
65
- def http
66
- http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
67
- http.use_ssl = ssl_implied?
68
-
69
- if options[:timeout] && options[:timeout].is_a?(Integer)
70
- http.open_timeout = options[:timeout]
71
- http.read_timeout = options[:timeout]
72
- end
73
-
74
- if options[:pem] && http.use_ssl?
75
- http.cert = OpenSSL::X509::Certificate.new(options[:pem])
76
- http.key = OpenSSL::PKey::RSA.new(options[:pem])
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
64
+ def http
65
+ http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
66
+ http.use_ssl = ssl_implied?
85
67
 
86
- http
68
+ if options[:timeout] && options[:timeout].is_a?(Integer)
69
+ http.open_timeout = options[:timeout]
70
+ http.read_timeout = options[:timeout]
87
71
  end
88
72
 
89
- def ssl_implied?
90
- uri.port == 443 || uri.instance_of?(URI::HTTPS)
73
+ if options[:pem] && http.use_ssl?
74
+ http.cert = OpenSSL::X509::Certificate.new(options[:pem])
75
+ http.key = OpenSSL::PKey::RSA.new(options[:pem])
76
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
77
+ else
78
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
91
79
  end
92
80
 
93
- def body
94
- options[:body].is_a?(Hash) ? options[:body].to_params : options[:body]
81
+ if options[:debug_output]
82
+ http.set_debug_output(options[:debug_output])
95
83
  end
96
84
 
97
- def username
98
- options[:basic_auth][:username]
99
- end
85
+ http
86
+ end
100
87
 
101
- def password
102
- options[:basic_auth][:password]
103
- end
88
+ def ssl_implied?
89
+ uri.port == 443 || uri.instance_of?(URI::HTTPS)
90
+ end
104
91
 
105
- def setup_raw_request
106
- @raw_request = http_method.new(uri.request_uri)
107
- @raw_request.body = body if body
108
- @raw_request.initialize_http_header(options[:headers])
109
- @raw_request.basic_auth(username, password) if options[:basic_auth]
110
- end
92
+ def body
93
+ options[:body].is_a?(Hash) ? options[:body].to_params : options[:body]
94
+ end
111
95
 
112
- def perform_actual_request
113
- http.request(@raw_request)
114
- end
96
+ def credentials
97
+ options[:basic_auth] || options[:digest_auth]
98
+ end
115
99
 
116
- def get_response
117
- self.last_response = perform_actual_request
118
- options[:format] ||= format_from_mimetype(last_response['content-type'])
100
+ def username
101
+ credentials[:username]
102
+ end
103
+
104
+ def password
105
+ credentials[:password]
106
+ end
107
+
108
+ def setup_raw_request
109
+ @raw_request = http_method.new(uri.request_uri)
110
+ @raw_request.body = body if body
111
+ @raw_request.initialize_http_header(options[:headers])
112
+ @raw_request.basic_auth(username, password) if options[:basic_auth]
113
+ setup_digest_auth if options[:digest_auth]
114
+ end
115
+
116
+ def setup_digest_auth
117
+ res = http.head(uri.request_uri, options[:headers])
118
+ if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
119
+ @raw_request.digest_auth(username, password, res)
119
120
  end
121
+ end
120
122
 
121
- def query_string(uri)
122
- query_string_parts = []
123
- query_string_parts << uri.query unless uri.query.nil?
123
+ def perform_actual_request
124
+ http.request(@raw_request)
125
+ end
124
126
 
125
- if options[:query].is_a?(Hash)
126
- query_string_parts << options[:default_params].merge(options[:query]).to_params
127
- else
128
- query_string_parts << options[:default_params].to_params unless options[:default_params].empty?
129
- query_string_parts << options[:query] unless options[:query].nil?
130
- end
127
+ def get_response
128
+ self.last_response = perform_actual_request
129
+ options[:format] ||= format_from_mimetype(last_response['content-type'])
130
+ end
131
131
 
132
- query_string_parts.size > 0 ? query_string_parts.join('&') : nil
132
+ def query_string(uri)
133
+ query_string_parts = []
134
+ query_string_parts << uri.query unless uri.query.nil?
135
+
136
+ if options[:query].is_a?(Hash)
137
+ query_string_parts << options[:default_params].merge(options[:query]).to_params
138
+ else
139
+ query_string_parts << options[:default_params].to_params unless options[:default_params].empty?
140
+ query_string_parts << options[:query] unless options[:query].nil?
133
141
  end
134
142
 
135
- # Raises exception Net::XXX (http error code) if an http error occured
136
- def handle_response
137
- case last_response
138
- when Net::HTTPMultipleChoice, # 300
139
- Net::HTTPMovedPermanently, # 301
140
- Net::HTTPFound, # 302
141
- Net::HTTPSeeOther, # 303
142
- Net::HTTPUseProxy, # 305
143
- Net::HTTPTemporaryRedirect
143
+ query_string_parts.size > 0 ? query_string_parts.join('&') : nil
144
+ end
145
+
146
+ # Raises exception Net::XXX (http error code) if an http error occured
147
+ def handle_response
148
+ case last_response
149
+ when Net::HTTPMultipleChoice, # 300
150
+ Net::HTTPMovedPermanently, # 301
151
+ Net::HTTPFound, # 302
152
+ Net::HTTPSeeOther, # 303
153
+ Net::HTTPUseProxy, # 305
154
+ Net::HTTPTemporaryRedirect
144
155
  if last_response.key?('location')
145
156
  options[:limit] -= 1
146
157
  self.path = last_response['location']
147
- @redirect = true
148
- self.http_method = Net::HTTP::Get
158
+ self.redirect = true
159
+ self.http_method = Net::HTTP::Get unless options[:maintain_method_across_redirects]
149
160
  capture_cookies(last_response)
150
161
  perform
151
162
  else
152
163
  last_response
153
164
  end
154
165
  else
155
- Response.new(parse_response(last_response.body), last_response.body, last_response.code, last_response.message, last_response.to_hash)
156
- end
166
+ Response.new(last_response, parse_response(last_response.body))
157
167
  end
168
+ end
158
169
 
159
- def parse_response(body)
160
- parser.call(body, format)
161
- end
170
+ def parse_response(body)
171
+ parser.call(body, format)
172
+ end
162
173
 
163
- def capture_cookies(response)
164
- return unless response['Set-Cookie']
165
- cookies_hash = HTTParty::CookieHash.new()
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
174
+ def capture_cookies(response)
175
+ return unless response['Set-Cookie']
176
+ cookies_hash = HTTParty::CookieHash.new()
177
+ cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
178
+ cookies_hash.add_cookies(response['Set-Cookie'])
179
+ options[:headers] ||= {}
180
+ options[:headers]['Cookie'] = cookies_hash.to_cookie_string
181
+ end
171
182
 
172
- # Uses the HTTP Content-Type header to determine the format of the
173
- # response It compares the MIME type returned to the types stored in the
174
- # SupportedFormats hash
175
- def format_from_mimetype(mimetype)
176
- if mimetype && parser.respond_to?(:format_from_mimetype)
177
- parser.format_from_mimetype(mimetype)
178
- end
183
+ # Uses the HTTP Content-Type header to determine the format of the
184
+ # response It compares the MIME type returned to the types stored in the
185
+ # SupportedFormats hash
186
+ def format_from_mimetype(mimetype)
187
+ if mimetype && parser.respond_to?(:format_from_mimetype)
188
+ parser.format_from_mimetype(mimetype)
179
189
  end
190
+ end
180
191
 
181
192
  def validate
182
193
  raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
183
194
  raise ArgumentError, 'only get, post, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
184
195
  raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
196
+ 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
197
  raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
198
+ raise ArgumentError, ':digest_auth must be a hash' if options[:digest_auth] && !options[:digest_auth].is_a?(Hash)
186
199
  raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].is_a?(Hash)
187
200
  end
188
201
 
189
- def post?
190
- Net::HTTP::Post == http_method
191
- end
202
+ def post?
203
+ Net::HTTP::Post == http_method
204
+ end
192
205
  end
193
206
  end
194
-
@@ -1,18 +1,62 @@
1
1
  module HTTParty
2
2
  class Response < HTTParty::BasicObject #:nodoc:
3
- attr_accessor :body, :code, :message, :headers
4
- attr_reader :delegate
5
-
6
- def initialize(delegate, body, code, message, headers={})
7
- @delegate = delegate
8
- @body = body
9
- @code = code.to_i
10
- @message = message
11
- @headers = headers
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
+ Object.instance_method(:class).bind(self).call
42
+ end
43
+
44
+ def code
45
+ response.code.to_i
46
+ end
47
+
48
+ def inspect
49
+ %(<#{self.class} @response=#{response.inspect}>)
12
50
  end
13
51
 
14
52
  def method_missing(name, *args, &block)
15
- @delegate.send(name, *args, &block)
53
+ if parsed_response.respond_to?(name)
54
+ parsed_response.send(name, *args, &block)
55
+ elsif response.respond_to?(name)
56
+ response.send(name, *args, &block)
57
+ else
58
+ super
59
+ end
16
60
  end
17
61
  end
18
62
  end
@@ -30,6 +30,17 @@ describe HTTParty::Request do
30
30
  @request.send(:setup_raw_request)
31
31
  @request.instance_variable_get(:@raw_request)['authorization'].should_not be_nil
32
32
  end
33
+
34
+ it "should use digest auth when configured" do
35
+ FakeWeb.register_uri(:head, "http://api.foo.com/v1",
36
+ :www_authenticate => 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false')
37
+
38
+ @request.options[:digest_auth] = {:username => 'foobar', :password => 'secret'}
39
+ @request.send(:setup_raw_request)
40
+
41
+ raw_request = @request.instance_variable_get(:@raw_request)
42
+ raw_request.instance_variable_get(:@header)['Authorization'].should_not be_nil
43
+ end
33
44
  end
34
45
 
35
46
  describe "#uri" do
@@ -353,6 +364,13 @@ describe HTTParty::Request do
353
364
  @request.perform.should == {"hash" => {"foo" => "bar"}}
354
365
  @request.http_method.should == Net::HTTP::Get
355
366
  end
367
+
368
+ it 'should not make resulting request a get request if options[:maintain_method_across_redirects] is true' do
369
+ @request.options[:maintain_method_across_redirects] = true
370
+ @request.http_method = Net::HTTP::Delete
371
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
372
+ @request.http_method.should == Net::HTTP::Delete
373
+ end
356
374
  end
357
375
 
358
376
  describe "infinitely" do
@@ -373,5 +391,25 @@ describe HTTParty::Request do
373
391
  }.should raise_error(ArgumentError)
374
392
  end
375
393
  end
394
+
395
+ describe "argument validation" do
396
+ it "should raise argument error if basic_auth and digest_auth are both present" do
397
+ lambda {
398
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => {}, :digest_auth => {}).perform
399
+ }.should raise_error(ArgumentError, "only one authentication method, :basic_auth or :digest_auth may be used at a time")
400
+ end
401
+
402
+ it "should raise argument error if basic_auth is not a hash" do
403
+ lambda {
404
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => ["foo", "bar"]).perform
405
+ }.should raise_error(ArgumentError, ":basic_auth must be a hash")
406
+ end
407
+
408
+ it "should raise argument error if digest_auth is not a hash" do
409
+ lambda {
410
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :digest_auth => ["foo", "bar"]).perform
411
+ }.should raise_error(ArgumentError, ":digest_auth must be a hash")
412
+ end
413
+ end
376
414
  end
377
415
 
@@ -1,62 +1,83 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
2
 
3
3
  describe HTTParty::Response do
4
+ before do
5
+ @last_modified = Date.new(2010, 1, 15).to_s
6
+ @content_length = '1024'
7
+ @response_object = {'foo' => 'bar'}
8
+ @response_object = Net::HTTPOK.new('1.1', 200, 'OK')
9
+ @response_object.stub(:body => "{foo:'bar'}")
10
+ @response_object['last-modified'] = @last_modified
11
+ @response_object['content-length'] = @content_length
12
+ @parsed_response = {"foo" => "bar"}
13
+ @response = HTTParty::Response.new(@response_object, @parsed_response)
14
+ end
15
+
4
16
  describe "initialization" do
5
- before do
6
- @response_object = {'foo' => 'bar'}
7
- @body = "{foo:'bar'}"
8
- @code = '200'
9
- @message = 'OK'
10
- @response = HTTParty::Response.new(@response_object, @body, @code, @message)
11
- end
12
-
13
- it "should set delegate" do
14
- @response.delegate.should == @response_object
17
+ it "should set the Net::HTTP Response" do
18
+ @response.response.should == @response_object
15
19
  end
16
-
20
+
17
21
  it "should set body" do
18
- @response.body.should == @body
22
+ @response.body.should == @response_object.body
19
23
  end
20
-
24
+
21
25
  it "should set code" do
22
- @response.code.should.to_s == @code
26
+ @response.code.should.to_s == @response_object.code
23
27
  end
24
28
 
25
29
  it "should set code as a Fixnum" do
26
30
  @response.code.should be_an_instance_of(Fixnum)
27
31
  end
28
-
29
- it "should set body" do
30
- @response.body.should == @body
31
- end
32
32
  end
33
-
34
- it "should be able to set headers during initialization" do
35
- response = HTTParty::Response.new({'foo' => 'bar'}, "{foo:'bar'}", 200, 'OK', {'foo' => 'bar'})
36
- response.headers.should == {'foo' => 'bar'}
33
+
34
+ it "returns response headers" do
35
+ response = HTTParty::Response.new(@response_object, @parsed_response)
36
+ response.headers.should == {'last-modified' => [@last_modified], 'content-length' => [@content_length]}
37
37
  end
38
-
38
+
39
39
  it "should send missing methods to delegate" do
40
- response = HTTParty::Response.new({'foo' => 'bar'}, "{foo:'bar'}", 200, 'OK')
40
+ response = HTTParty::Response.new(@response_object, {'foo' => 'bar'})
41
41
  response['foo'].should == 'bar'
42
42
  end
43
-
44
- it "should be able to iterate delegate if it is array" do
45
- response = HTTParty::Response.new([{'foo' => 'bar'}, {'foo' => 'baz'}], "[{foo:'bar'}, {foo:'baz'}]", 200, 'OK')
43
+
44
+ it "should be able to iterate if it is array" do
45
+ response = HTTParty::Response.new(@response_object, [{'foo' => 'bar'}, {'foo' => 'baz'}])
46
46
  response.size.should == 2
47
- lambda {
47
+ expect {
48
48
  response.each { |item| }
49
- }.should_not raise_error
49
+ }.to_not raise_error
50
+ end
51
+
52
+ it "allows headers to be accessed by mixed-case names in hash notation" do
53
+ response = HTTParty::Response.new(@response_object, @parsed_response)
54
+ response.headers['Content-LENGTH'].should == @content_length
55
+ end
56
+
57
+ it "returns a comma-delimited value when multiple values exist" do
58
+ @response_object.add_field 'set-cookie', 'csrf_id=12345; path=/'
59
+ @response_object.add_field 'set-cookie', '_github_ses=A123CdE; path=/'
60
+ response = HTTParty::Response.new(@response_object, @parsed_response)
61
+ response.headers['set-cookie'].should == "csrf_id=12345; path=/, _github_ses=A123CdE; path=/"
50
62
  end
51
-
63
+
64
+ # Backwards-compatibility - previously, #headers returned a Hash
65
+ it "responds to hash methods" do
66
+ response = HTTParty::Response.new(@response_object, @parsed_response)
67
+ hash_methods = {}.methods - response.headers.methods
68
+ hash_methods.each do |method_name|
69
+ response.headers.respond_to?(method_name).should be_true
70
+ end
71
+ end
72
+
52
73
  xit "should allow hashes to be accessed with dot notation" do
53
74
  response = HTTParty::Response.new({'foo' => 'bar'}, "{foo:'bar'}", 200, 'OK')
54
75
  response.foo.should == 'bar'
55
76
  end
56
-
77
+
57
78
  xit "should allow nested hashes to be accessed with dot notation" do
58
79
  response = HTTParty::Response.new({'foo' => {'bar' => 'baz'}}, "{foo: {bar:'baz'}}", 200, 'OK')
59
80
  response.foo.should == {'bar' => 'baz'}
60
81
  response.foo.bar.should == 'baz'
61
82
  end
62
- end
83
+ end
@@ -203,6 +203,17 @@ describe HTTParty do
203
203
  end
204
204
  end
205
205
 
206
+ describe "default timeout" do
207
+ it "should default to nil" do
208
+ @klass.default_options[:timeout].should == nil
209
+ end
210
+
211
+ it "should support updating" do
212
+ @klass.default_timeout 10
213
+ @klass.default_options[:timeout].should == 10
214
+ end
215
+ end
216
+
206
217
  describe "debug_output" do
207
218
  it "stores the given stream as a default_option" do
208
219
  @klass.debug_output $stdout
@@ -222,6 +233,13 @@ describe HTTParty do
222
233
  end
223
234
  end
224
235
 
236
+ describe "digest http authentication" do
237
+ it "should work" do
238
+ @klass.digest_auth 'foobar', 'secret'
239
+ @klass.default_options[:digest_auth].should == {:username => 'foobar', :password => 'secret'}
240
+ end
241
+ end
242
+
225
243
  describe "parser" do
226
244
  let(:parser) do
227
245
  Proc.new{ |data, format| CustomParser.parse(data) }
@@ -243,7 +261,7 @@ describe HTTParty do
243
261
  @klass.format :json
244
262
  class MyParser < HTTParty::Parser
245
263
  SupportedFormats = {}
246
- end
264
+ end unless defined?(MyParser)
247
265
  expect do
248
266
  @klass.parser MyParser
249
267
  end.to raise_error(HTTParty::UnsupportedFormat)
@@ -316,6 +334,18 @@ describe HTTParty do
316
334
  end
317
335
  end
318
336
 
337
+ describe "#maintain_method_across_redirects" do
338
+ it "sets maintain_method_across_redirects to true by default" do
339
+ @klass.maintain_method_across_redirects
340
+ @klass.default_options[:maintain_method_across_redirects].should be_true
341
+ end
342
+
343
+ it "sets the maintain_method_across_redirects option to false" do
344
+ @klass.maintain_method_across_redirects false
345
+ @klass.default_options[:maintain_method_across_redirects].should be_false
346
+ end
347
+ end
348
+
319
349
  describe "with explicit override of automatic redirect handling" do
320
350
  before do
321
351
  @request = HTTParty::Request.new(Net::HTTP::Get, 'http://api.foo.com/v1', :format => :xml, :no_follow => true)
@@ -2,8 +2,6 @@ require File.join(File.dirname(__FILE__), '..', 'lib', 'httparty')
2
2
  require 'spec/autorun'
3
3
  require 'fakeweb'
4
4
 
5
- FakeWeb.allow_net_connect = false
6
-
7
5
  def file_fixture(filename)
8
6
  open(File.join(File.dirname(__FILE__), 'fixtures', "#{filename.to_s}")).read
9
7
  end
@@ -12,4 +10,10 @@ Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].e
12
10
 
13
11
  Spec::Runner.configure do |config|
14
12
  config.include HTTParty::StubResponse
13
+ config.before(:suite) do
14
+ FakeWeb.allow_net_connect = false
15
+ end
16
+ config.after(:suite) do
17
+ FakeWeb.allow_net_connect = true
18
+ end
15
19
  end
@@ -14,7 +14,7 @@ module HTTParty
14
14
  end
15
15
 
16
16
  def stub_response(body, code = 200)
17
- unless @http
17
+ unless defined?(@http) && @http
18
18
  @http = Net::HTTP.new('localhost', 80)
19
19
  @request.stub!(:http).and_return(@http)
20
20
  @request.stub!(:uri).and_return(URI.parse("http://foo.com/foobar"))
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 5
8
- - 2
9
8
  - 3
10
- version: 0.5.2.3
9
+ - 4
10
+ version: 0.5.3.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - John Nunemaker
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-06-11 00:00:00 +08:00
19
+ date: 2010-05-26 00:00:00 +08:00
20
20
  default_executable: httparty
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -29,8 +29,8 @@ dependencies:
29
29
  segments:
30
30
  - 0
31
31
  - 1
32
- - 6
33
- version: 0.1.6
32
+ - 7
33
+ version: 0.1.7
34
34
  type: :runtime
35
35
  version_requirements: *id001
36
36
  - !ruby/object:Gem::Dependency
@@ -55,8 +55,8 @@ dependencies:
55
55
  - !ruby/object:Gem::Version
56
56
  segments:
57
57
  - 0
58
- - 4
59
- version: "0.4"
58
+ - 7
59
+ version: "0.7"
60
60
  type: :development
61
61
  version_requirements: *id003
62
62
  - !ruby/object:Gem::Dependency
@@ -90,13 +90,12 @@ dependencies:
90
90
  prerelease: false
91
91
  requirement: &id006 !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - "="
93
+ - - ~>
94
94
  - !ruby/object:Gem::Version
95
95
  segments:
96
96
  - 1
97
- - 2
98
- - 9
99
- version: 1.2.9
97
+ - 3
98
+ version: "1.3"
100
99
  type: :development
101
100
  version_requirements: *id006
102
101
  description: Makes http fun! Also, makes consuming restful web services dead easy.
@@ -128,6 +127,7 @@ files:
128
127
  - features/basic_authentication.feature
129
128
  - features/command_line.feature
130
129
  - features/deals_with_http_error_codes.feature
130
+ - features/digest_authentication.feature
131
131
  - features/handles_multiple_formats.feature
132
132
  - features/steps/env.rb
133
133
  - features/steps/httparty_response_steps.rb
@@ -142,6 +142,7 @@ files:
142
142
  - lib/httparty/core_extensions.rb
143
143
  - lib/httparty/exceptions.rb
144
144
  - lib/httparty/module_inheritable_attributes.rb
145
+ - lib/httparty/net_digest_auth.rb
145
146
  - lib/httparty/parser.rb
146
147
  - lib/httparty/request.rb
147
148
  - lib/httparty/response.rb
@@ -162,7 +163,7 @@ files:
162
163
  - website/css/common.css
163
164
  - website/index.html
164
165
  has_rdoc: true
165
- homepage: http://httparty.rubyforge.org
166
+ homepage: http://github.com/jugend/httparty
166
167
  licenses: []
167
168
 
168
169
  post_install_message: When you HTTParty, you must party hard!