jugend-httparty 0.5.2.3 → 0.5.3.4

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.
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!