httparty 0.13.0 → 0.14.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.

Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +92 -0
  4. data/.rubocop_todo.yml +124 -0
  5. data/.simplecov +1 -0
  6. data/.travis.yml +4 -2
  7. data/CONTRIBUTING.md +23 -0
  8. data/Gemfile +8 -3
  9. data/Guardfile +3 -3
  10. data/History +106 -11
  11. data/README.md +19 -20
  12. data/Rakefile +5 -7
  13. data/bin/httparty +18 -14
  14. data/docs/README.md +100 -0
  15. data/examples/README.md +67 -0
  16. data/examples/aaws.rb +5 -5
  17. data/examples/basic.rb +6 -10
  18. data/examples/crack.rb +2 -2
  19. data/examples/custom_parsers.rb +1 -4
  20. data/examples/delicious.rb +8 -8
  21. data/examples/google.rb +2 -2
  22. data/examples/headers_and_user_agents.rb +1 -1
  23. data/examples/logging.rb +36 -0
  24. data/examples/nokogiri_html_parser.rb +0 -3
  25. data/examples/rescue_json.rb +17 -0
  26. data/examples/rubyurl.rb +3 -3
  27. data/examples/stackexchange.rb +24 -0
  28. data/examples/tripit_sign_in.rb +20 -9
  29. data/examples/twitter.rb +7 -7
  30. data/examples/whoismyrep.rb +1 -1
  31. data/features/command_line.feature +90 -2
  32. data/features/digest_authentication.feature +10 -0
  33. data/features/steps/env.rb +16 -11
  34. data/features/steps/httparty_response_steps.rb +18 -14
  35. data/features/steps/httparty_steps.rb +10 -2
  36. data/features/steps/mongrel_helper.rb +35 -2
  37. data/features/steps/remote_service_steps.rb +26 -8
  38. data/features/supports_read_timeout_option.feature +13 -0
  39. data/httparty.gemspec +6 -5
  40. data/lib/httparty/connection_adapter.rb +36 -13
  41. data/lib/httparty/cookie_hash.rb +3 -4
  42. data/lib/httparty/exceptions.rb +4 -1
  43. data/lib/httparty/hash_conversions.rb +17 -15
  44. data/lib/httparty/logger/{apache_logger.rb → apache_formatter.rb} +3 -3
  45. data/lib/httparty/logger/curl_formatter.rb +91 -0
  46. data/lib/httparty/logger/logger.rb +18 -10
  47. data/lib/httparty/module_inheritable_attributes.rb +1 -1
  48. data/lib/httparty/net_digest_auth.rb +69 -18
  49. data/lib/httparty/parser.rb +4 -2
  50. data/lib/httparty/request.rb +105 -48
  51. data/lib/httparty/response.rb +31 -6
  52. data/lib/httparty/version.rb +1 -1
  53. data/lib/httparty.rb +132 -72
  54. data/spec/httparty/connection_adapter_spec.rb +285 -88
  55. data/spec/httparty/cookie_hash_spec.rb +46 -29
  56. data/spec/httparty/exception_spec.rb +29 -7
  57. data/spec/httparty/hash_conversions_spec.rb +49 -0
  58. data/spec/httparty/logger/apache_formatter_spec.rb +41 -0
  59. data/spec/httparty/logger/curl_formatter_spec.rb +119 -0
  60. data/spec/httparty/logger/logger_spec.rb +23 -7
  61. data/spec/httparty/net_digest_auth_spec.rb +118 -30
  62. data/spec/httparty/parser_spec.rb +43 -35
  63. data/spec/httparty/request_spec.rb +734 -182
  64. data/spec/httparty/response_spec.rb +139 -69
  65. data/spec/httparty/ssl_spec.rb +22 -22
  66. data/spec/httparty_spec.rb +307 -199
  67. data/spec/spec_helper.rb +34 -12
  68. data/spec/support/ssl_test_helper.rb +6 -6
  69. data/spec/support/ssl_test_server.rb +21 -21
  70. data/spec/support/stub_response.rb +20 -14
  71. data/website/index.html +3 -3
  72. metadata +30 -33
  73. data/lib/httparty/core_extensions.rb +0 -32
  74. data/lib/httparty/logger/curl_logger.rb +0 -48
  75. data/spec/httparty/logger/apache_logger_spec.rb +0 -26
  76. data/spec/httparty/logger/curl_logger_spec.rb +0 -18
  77. data/spec/spec.opts +0 -2
@@ -1,22 +1,27 @@
1
1
  require 'mongrel'
2
2
  require './lib/httparty'
3
- require 'spec/expectations'
3
+ require 'rspec/expectations'
4
+ require 'aruba/cucumber'
4
5
 
5
- Before do
6
- def new_port
7
- server = TCPServer.new('0.0.0.0', nil)
8
- port = server.addr[1]
9
- ensure
10
- server.close
11
- end
12
-
13
- port = ENV["HTTPARTY_PORT"] || new_port
6
+ def run_server(port)
14
7
  @host_and_port = "0.0.0.0:#{port}"
15
8
  @server = Mongrel::HttpServer.new("0.0.0.0", port)
16
9
  @server.run
17
10
  @request_options = {}
18
11
  end
19
12
 
13
+ def new_port
14
+ server = TCPServer.new('0.0.0.0', nil)
15
+ port = server.addr[1]
16
+ ensure
17
+ server.close
18
+ end
19
+
20
+ Before('~@command_line') do
21
+ port = ENV["HTTPARTY_PORT"] || new_port
22
+ run_server(port)
23
+ end
24
+
20
25
  After do
21
- @server.stop
26
+ @server.stop if @server
22
27
  end
@@ -11,42 +11,46 @@ def constantize(camel_cased_word)
11
11
  constant
12
12
  end
13
13
 
14
- Then /it should return an? (\w+)$/ do |class_string|
15
- @response_from_httparty.should be_an_instance_of(class_string.class)
14
+ Then /it should return an? ([\w\:]+)$/ do |class_string|
15
+ expect(@response_from_httparty.parsed_response).to be_a(Object.const_get(class_string))
16
16
  end
17
17
 
18
18
  Then /the return value should match '(.*)'/ do |expected_text|
19
- @response_from_httparty.should eql(expected_text)
19
+ expect(@response_from_httparty.parsed_response).to eq(expected_text)
20
20
  end
21
21
 
22
22
  Then /it should return a Hash equaling:/ do |hash_table|
23
- @response_from_httparty.should be_an_instance_of(Hash)
24
- @response_from_httparty.keys.length.should eql(hash_table.rows.length)
23
+ expect(@response_from_httparty.parsed_response).to be_a(Hash)
24
+ expect(@response_from_httparty.keys.length).to eq(hash_table.rows.length)
25
25
  hash_table.hashes.each do |pair|
26
26
  key, value = pair["key"], pair["value"]
27
- @response_from_httparty.keys.should include(key)
28
- @response_from_httparty[key].should eql(value)
27
+ expect(@response_from_httparty.keys).to include(key)
28
+ expect(@response_from_httparty[key]).to eq(value)
29
29
  end
30
30
  end
31
31
 
32
32
  Then /it should return an Array equaling:/ do |array|
33
- @response_from_httparty.should be_an_instance_of(Array)
34
- @response_from_httparty.should eql array.raw
33
+ expect(@response_from_httparty.parsed_response).to be_a(Array)
34
+ expect(@response_from_httparty.parsed_response).to eq(array.raw)
35
35
  end
36
36
 
37
37
  Then /it should return a response with a (\d+) response code/ do |code|
38
- @response_from_httparty.code.should eql(code.to_i)
38
+ expect(@response_from_httparty.code).to eq(code.to_i)
39
39
  end
40
40
 
41
41
  Then /it should return a response with a (.*) content\-encoding$/ do |content_type|
42
- @response_from_httparty.headers['content-encoding'].should eql('gzip')
42
+ expect(@response_from_httparty.headers['content-encoding']).to eq('gzip')
43
43
  end
44
44
 
45
45
  Then /it should return a response with a blank body$/ do
46
- @response_from_httparty.body.should be(nil)
46
+ expect(@response_from_httparty.body).to be_nil
47
47
  end
48
48
 
49
49
  Then /it should raise (?:an|a) ([\w:]+) exception/ do |exception|
50
- @exception_from_httparty.should_not be_nil
51
- @exception_from_httparty.should be_a constantize(exception)
50
+ expect(@exception_from_httparty).to_not be_nil
51
+ expect(@exception_from_httparty).to be_a constantize(exception)
52
+ end
53
+
54
+ Then /it should not raise (?:an|a) ([\w:]+) exception/ do |exception|
55
+ expect(@exception_from_httparty).to be_nil
52
56
  end
@@ -2,6 +2,14 @@ When /^I set my HTTParty timeout option to (\d+)$/ do |timeout|
2
2
  @request_options[:timeout] = timeout.to_i
3
3
  end
4
4
 
5
+ When /^I set my HTTParty open_timeout option to (\d+)$/ do |timeout|
6
+ @request_options[:open_timeout] = timeout.to_i
7
+ end
8
+
9
+ When /^I set my HTTParty read_timeout option to (\d+)$/ do |timeout|
10
+ @request_options[:read_timeout] = timeout.to_i
11
+ end
12
+
5
13
  When /I call HTTParty#get with '(.*)'$/ do |url|
6
14
  begin
7
15
  @response_from_httparty = HTTParty.get("http://#{@host_and_port}#{url}", @request_options)
@@ -22,7 +30,7 @@ When /I call HTTParty#get with '(.*)' and a basic_auth hash:/ do |url, auth_tabl
22
30
  h = auth_table.hashes.first
23
31
  @response_from_httparty = HTTParty.get(
24
32
  "http://#{@host_and_port}#{url}",
25
- :basic_auth => { :username => h["username"], :password => h["password"] }
33
+ basic_auth: { username: h["username"], password: h["password"] }
26
34
  )
27
35
  end
28
36
 
@@ -30,6 +38,6 @@ When /I call HTTParty#get with '(.*)' and a digest_auth hash:/ do |url, auth_tab
30
38
  h = auth_table.hashes.first
31
39
  @response_from_httparty = HTTParty.get(
32
40
  "http://#{@host_and_port}#{url}",
33
- :digest_auth => { :username => h["username"], :password => h["password"] }
41
+ digest_auth: { username: h["username"], password: h["password"] }
34
42
  )
35
43
  end
@@ -10,14 +10,14 @@ class BasicMongrelHandler < Mongrel::HttpHandler
10
10
  end
11
11
 
12
12
  def process(request, response)
13
- instance_eval &preprocessor if preprocessor
13
+ instance_eval(&preprocessor) if preprocessor
14
14
  reply_with(response, response_code, response_body)
15
15
  end
16
16
 
17
17
  def reply_with(response, code, response_body)
18
18
  response.start(code) do |head, body|
19
19
  head["Content-Type"] = content_type
20
- custom_headers.each { |k,v| head[k] = v }
20
+ custom_headers.each { |k, v| head[k] = v }
21
21
  body.write(response_body)
22
22
  end
23
23
  end
@@ -88,6 +88,39 @@ module DigestAuthentication
88
88
  end
89
89
  end
90
90
 
91
+ module DigestAuthenticationUsingMD5Sess
92
+ NONCE = 'nonce'
93
+ REALM = 'testrealm@host.com'
94
+ QOP = 'auth,auth-int'
95
+ def self.extended(base)
96
+ base.custom_headers["WWW-Authenticate"] = %(Digest realm="#{REALM}",qop="#{QOP}",algorithm="MD5-sess",nonce="#{NONCE}",opaque="opaque"')
97
+ end
98
+
99
+ def process(request, response)
100
+ if authorized?(request)
101
+ super
102
+ else
103
+ reply_with(response, 401, "Incorrect. You have 20 seconds to comply.")
104
+ end
105
+ end
106
+
107
+ def md5(str)
108
+ Digest::MD5.hexdigest(str)
109
+ end
110
+
111
+ def authorized?(request)
112
+ auth = request.params["HTTP_AUTHORIZATION"]
113
+ params = {}
114
+ auth.to_s.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }.gsub(/(\w+)=([^,]*)/) { params[$1] = $2 }
115
+ a1a = [@username,REALM,@password].join(':')
116
+ a1 = [md5(a1a),NONCE,params['cnonce'] ].join(':')
117
+ a2 = [ request.params["REQUEST_METHOD"], request.params["REQUEST_URI"] ] .join(':')
118
+ expected_response = md5( [md5(a1), NONCE, params['nc'], params['cnonce'], QOP, md5(a2)].join(':') )
119
+ expected_response == params['response']
120
+ end
121
+ end
122
+
123
+
91
124
  def new_mongrel_redirector(target_url, relative_path = false)
92
125
  target_url = "http://#{@host_and_port}#{target_url}" unless relative_path
93
126
  Mongrel::RedirectHandler.new(target_url)
@@ -1,6 +1,6 @@
1
1
  Given /a remote service that returns '(.*)'/ do |response_body|
2
2
  @handler = BasicMongrelHandler.new
3
- Given "the response from the service has a body of '#{response_body}'"
3
+ step "the response from the service has a body of '#{response_body}'"
4
4
  end
5
5
 
6
6
  Given /^a remote service that returns:$/ do |response_body|
@@ -17,15 +17,22 @@ Given /that service is accessed at the path '(.*)'/ do |path|
17
17
  @server.register(path, @handler)
18
18
  end
19
19
 
20
- Given /^that service takes (\d+) seconds to generate a response$/ do |time|
21
- @server_response_time = time.to_i
22
- @handler.preprocessor = Proc.new { sleep time.to_i }
20
+ Given /^that service takes (\d+) (.*) to generate a response$/ do |time, unit|
21
+ time = time.to_i
22
+ time *= 60 if unit =~ /minute/
23
+ @server_response_time = time
24
+ @handler.preprocessor = proc { sleep time }
23
25
  end
24
26
 
25
27
  Given /^a remote deflate service$/ do
26
28
  @handler = DeflateHandler.new
27
29
  end
28
30
 
31
+ Given /^a remote deflate service on port '(\d+)'/ do |port|
32
+ run_server(port)
33
+ @handler = DeflateHandler.new
34
+ end
35
+
29
36
  Given /^a remote gzip service$/ do
30
37
  @handler = GzipHandler.new
31
38
  end
@@ -50,16 +57,27 @@ Given /that service is protected by Digest Authentication/ do
50
57
  @handler.extend DigestAuthentication
51
58
  end
52
59
 
60
+ Given /that service is protected by MD5-sess Digest Authentication/ do
61
+ @handler.extend DigestAuthenticationUsingMD5Sess
62
+ end
63
+
53
64
  Given /that service requires the username '(.*)' with the password '(.*)'/ do |username, password|
54
65
  @handler.username = username
55
66
  @handler.password = password
56
67
  end
57
68
 
69
+ # customize aruba cucumber step
70
+ Then /^the output should contain '(.*)'$/ do |expected|
71
+ expect(all_commands.map(&:output).join("\n")).to match_output_string(expected)
72
+ end
73
+
58
74
  Given /a restricted page at '(.*)'/ do |url|
59
- Given "a remote service that returns 'A response I will never see'"
60
- And "that service is accessed at the path '#{url}'"
61
- And "that service is protected by Basic Authentication"
62
- And "that service requires the username 'something' with the password 'secret'"
75
+ steps "
76
+ Given a remote service that returns 'A response I will never see'
77
+ And that service is accessed at the path '#{url}'
78
+ And that service is protected by Basic Authentication
79
+ And that service requires the username 'something' with the password 'secret'
80
+ "
63
81
  end
64
82
 
65
83
  # This joins the server thread, and halts cucumber, so you can actually hit the
@@ -0,0 +1,13 @@
1
+ Feature: Supports the read timeout option
2
+ In order to handle inappropriately slow response times
3
+ As a developer
4
+ I want my request to raise an exception after my specified read_timeout as elapsed
5
+
6
+ Scenario: A long running response
7
+ Given a remote service that returns '<h1>Some HTML</h1>'
8
+ And that service is accessed at the path '/long_running_service.html'
9
+ And that service takes 2 seconds to generate a response
10
+ When I set my HTTParty read_timeout option to 1
11
+ And I call HTTParty#get with '/long_running_service.html'
12
+ Then it should raise a Timeout::Error exception
13
+ And I wait for the server to recover
data/httparty.gemspec CHANGED
@@ -1,26 +1,27 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.push File.expand_path("../lib", __FILE__)
3
3
  require "httparty/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "httparty"
7
7
  s.version = HTTParty::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
+ s.licenses = ['MIT']
9
10
  s.authors = ["John Nunemaker", "Sandro Turriate"]
10
11
  s.email = ["nunemaker@gmail.com"]
11
12
  s.homepage = "http://jnunemaker.github.com/httparty"
12
- s.summary = %q{Makes http fun! Also, makes consuming restful web services dead easy.}
13
- s.description = %q{Makes http fun! Also, makes consuming restful web services dead easy.}
13
+ s.summary = 'Makes http fun! Also, makes consuming restful web services dead easy.'
14
+ s.description = 'Makes http fun! Also, makes consuming restful web services dead easy.'
14
15
 
15
16
  s.required_ruby_version = '>= 1.9.3'
16
17
 
17
- s.add_dependency 'json', "~> 1.8"
18
18
  s.add_dependency 'multi_xml', ">= 0.5.2"
19
19
 
20
+ # If this line is removed, all hard partying will cease.
20
21
  s.post_install_message = "When you HTTParty, you must party hard!"
21
22
 
22
23
  s.files = `git ls-files`.split("\n")
23
24
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
25
26
  s.require_paths = ["lib"]
26
27
  end
@@ -38,18 +38,26 @@ module HTTParty
38
38
  # in the #options attribute. It is up to you to interpret them within your
39
39
  # connection adapter. Take a look at the implementation of
40
40
  # HTTParty::ConnectionAdapter#connection for examples of how they are used.
41
- # Something are probably interesting are as follows:
41
+ # Some things that are probably interesting are as follows:
42
42
  # * :+timeout+: timeout in seconds
43
+ # * :+open_timeout+: http connection open_timeout in seconds, overrides timeout if set
44
+ # * :+read_timeout+: http connection read_timeout in seconds, overrides timeout if set
43
45
  # * :+debug_output+: see HTTParty::ClassMethods.debug_output.
44
46
  # * :+pem+: contains pem data. see HTTParty::ClassMethods.pem.
47
+ # * :+verify+: verify the server’s certificate against the ca certificate.
48
+ # * :+verify_peer+: set to false to turn off server verification but still send client certificate
45
49
  # * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file.
46
50
  # * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path.
47
- # * :+connection_adapter_options+: contains the hash your passed to HTTParty.connection_adapter when you configured your connection adapter
51
+ # * :+connection_adapter_options+: contains the hash you passed to HTTParty.connection_adapter when you configured your connection adapter
48
52
  class ConnectionAdapter
49
-
50
53
  # Private: Regex used to strip brackets from IPv6 URIs.
51
54
  StripIpv6BracketsRegex = /\A\[(.*)\]\z/
52
55
 
56
+ OPTION_DEFAULTS = {
57
+ verify: true,
58
+ verify_peer: true
59
+ }
60
+
53
61
  # Public
54
62
  def self.call(uri, options)
55
63
  new(uri, options).connection
@@ -57,19 +65,21 @@ module HTTParty
57
65
 
58
66
  attr_reader :uri, :options
59
67
 
60
- def initialize(uri, options={})
61
- raise ArgumentError, "uri must be a URI, not a #{uri.class}" unless uri.kind_of? URI
68
+ def initialize(uri, options = {})
69
+ uri_adapter = options[:uri_adapter] || URI
70
+ raise ArgumentError, "uri must be a #{uri_adapter}, not a #{uri.class}" unless uri.is_a? uri_adapter
62
71
 
63
72
  @uri = uri
64
- @options = options
73
+ @options = OPTION_DEFAULTS.merge(options)
65
74
  end
66
75
 
67
76
  def connection
68
77
  host = clean_host(uri.host)
69
- if options[:http_proxyaddr]
70
- http = Net::HTTP.new(host, uri.port, options[:http_proxyaddr], options[:http_proxyport], options[:http_proxyuser], options[:http_proxypass])
78
+ port = uri.port || (uri.scheme == 'https' ? 443 : 80)
79
+ if options.key?(:http_proxyaddr)
80
+ http = Net::HTTP.new(host, port, options[:http_proxyaddr], options[:http_proxyport], options[:http_proxyuser], options[:http_proxypass])
71
81
  else
72
- http = Net::HTTP.new(host, uri.port)
82
+ http = Net::HTTP.new(host, port)
73
83
  end
74
84
 
75
85
  http.use_ssl = ssl_implied?(uri)
@@ -81,6 +91,14 @@ module HTTParty
81
91
  http.read_timeout = options[:timeout]
82
92
  end
83
93
 
94
+ if options[:read_timeout] && (options[:read_timeout].is_a?(Integer) || options[:read_timeout].is_a?(Float))
95
+ http.read_timeout = options[:read_timeout]
96
+ end
97
+
98
+ if options[:open_timeout] && (options[:open_timeout].is_a?(Integer) || options[:open_timeout].is_a?(Float))
99
+ http.open_timeout = options[:open_timeout]
100
+ end
101
+
84
102
  if options[:debug_output]
85
103
  http.set_debug_output(options[:debug_output])
86
104
  end
@@ -108,7 +126,7 @@ module HTTParty
108
126
  end
109
127
  end
110
128
 
111
- return http
129
+ http
112
130
  end
113
131
 
114
132
  private
@@ -122,7 +140,11 @@ module HTTParty
122
140
  end
123
141
 
124
142
  def ssl_implied?(uri)
125
- uri.port == 443 || uri.instance_of?(URI::HTTPS)
143
+ uri.port == 443 || uri.scheme == 'https'
144
+ end
145
+
146
+ def verify_ssl_certificate?
147
+ !(options[:verify] == false || options[:verify_peer] == false)
126
148
  end
127
149
 
128
150
  def attach_ssl_certificates(http, options)
@@ -141,10 +163,11 @@ module HTTParty
141
163
  end
142
164
 
143
165
  # Client certificate authentication
166
+ # Note: options[:pem] must contain the content of a PEM file having the private key appended
144
167
  if options[:pem]
145
168
  http.cert = OpenSSL::X509::Certificate.new(options[:pem])
146
169
  http.key = OpenSSL::PKey::RSA.new(options[:pem], options[:pem_password])
147
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
170
+ http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
148
171
  end
149
172
 
150
173
  # PKCS12 client certificate authentication
@@ -152,7 +175,7 @@ module HTTParty
152
175
  p12 = OpenSSL::PKCS12.new(options[:p12], options[:p12_password])
153
176
  http.cert = p12.certificate
154
177
  http.key = p12.key
155
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
178
+ http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
156
179
  end
157
180
 
158
181
  # SSL certificate authority file and/or directory
@@ -1,6 +1,5 @@
1
1
  class HTTParty::CookieHash < Hash #:nodoc:
2
-
3
- CLIENT_COOKIES = %w{path expires domain path secure HTTPOnly}
2
+ CLIENT_COOKIES = %w(path expires domain path secure httponly)
4
3
 
5
4
  def add_cookies(value)
6
5
  case value
@@ -8,7 +7,7 @@ class HTTParty::CookieHash < Hash #:nodoc:
8
7
  merge!(value)
9
8
  when String
10
9
  value.split('; ').each do |cookie|
11
- array = cookie.split('=',2)
10
+ array = cookie.split('=', 2)
12
11
  self[array[0].to_sym] = array[1]
13
12
  end
14
13
  else
@@ -17,6 +16,6 @@ class HTTParty::CookieHash < Hash #:nodoc:
17
16
  end
18
17
 
19
18
  def to_cookie_string
20
- delete_if { |k, v| CLIENT_COOKIES.include?(k.to_s.downcase) }.collect { |k, v| "#{k}=#{v}" }.join("; ")
19
+ reject { |k, v| CLIENT_COOKIES.include?(k.to_s.downcase) }.collect { |k, v| "#{k}=#{v}" }.join("; ")
21
20
  end
22
21
  end
@@ -2,7 +2,7 @@ module HTTParty
2
2
  # @abstact Exceptions raised by HTTParty inherit from Error
3
3
  class Error < StandardError; end
4
4
 
5
- # Exception raised when you attempt to set a non-existant format
5
+ # Exception raised when you attempt to set a non-existent format
6
6
  class UnsupportedFormat < Error; end
7
7
 
8
8
  # Exception raised when using a URI scheme other than HTTP or HTTPS
@@ -26,4 +26,7 @@ module HTTParty
26
26
  # Exception that is raised when request has redirected too many times.
27
27
  # Calling {#response} returns the Net:HTTP response object.
28
28
  class RedirectionTooDeep < ResponseError; end
29
+
30
+ # Exception that is raised when request redirects and location header is present more than once
31
+ class DuplicateLocationHeader < ResponseError; end
29
32
  end
@@ -3,18 +3,16 @@ module HTTParty
3
3
  # @return <String> This hash as a query string
4
4
  #
5
5
  # @example
6
- # { :name => "Bob",
7
- # :address => {
8
- # :street => '111 Ruby Ave.',
9
- # :city => 'Ruby Central',
10
- # :phones => ['111-111-1111', '222-222-2222']
6
+ # { name: "Bob",
7
+ # address: {
8
+ # street: '111 Ruby Ave.',
9
+ # city: 'Ruby Central',
10
+ # phones: ['111-111-1111', '222-222-2222']
11
11
  # }
12
12
  # }.to_params
13
13
  # #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave."
14
14
  def self.to_params(hash)
15
- params = hash.map { |k,v| normalize_param(k,v) }.join
16
- params.chop! # trailing &
17
- params
15
+ hash.to_hash.map { |k, v| normalize_param(k, v) }.join.chop
18
16
  end
19
17
 
20
18
  # @param key<Object> The key for the param.
@@ -27,18 +25,22 @@ module HTTParty
27
25
  param = ''
28
26
  stack = []
29
27
 
30
- if value.is_a?(Array)
31
- param << value.map { |element| normalize_param("#{key}[]", element) }.join
32
- elsif value.is_a?(Hash)
33
- stack << [key,value]
28
+ if value.respond_to?(:to_ary)
29
+ param << if value.empty?
30
+ "#{key}[]=&"
31
+ else
32
+ value.to_ary.map { |element| normalize_param("#{key}[]", element) }.join
33
+ end
34
+ elsif value.respond_to?(:to_hash)
35
+ stack << [key, value.to_hash]
34
36
  else
35
- param << "#{key}=#{URI.encode(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}&"
37
+ param << "#{key}=#{ERB::Util.url_encode(value.to_s)}&"
36
38
  end
37
39
 
38
40
  stack.each do |parent, hash|
39
41
  hash.each do |k, v|
40
- if v.is_a?(Hash)
41
- stack << ["#{parent}[#{k}]", v]
42
+ if v.respond_to?(:to_hash)
43
+ stack << ["#{parent}[#{k}]", v.to_hash]
42
44
  else
43
45
  param << normalize_param("#{parent}[#{k}]", v)
44
46
  end
@@ -1,6 +1,6 @@
1
1
  module HTTParty
2
2
  module Logger
3
- class ApacheLogger #:nodoc:
3
+ class ApacheFormatter #:nodoc:
4
4
  TAG_NAME = HTTParty.name
5
5
 
6
6
  attr_accessor :level, :logger, :current_time
@@ -14,8 +14,8 @@ module HTTParty
14
14
  current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
15
15
  http_method = request.http_method.name.split("::").last.upcase
16
16
  path = request.path.to_s
17
- content_length = response['Content-Length']
18
- @logger.send @level, "[#{TAG_NAME}] [#{current_time}] #{response.code} \"#{http_method} #{path}\" #{content_length || "-"} "
17
+ content_length = response.respond_to?(:headers) ? response.headers['Content-Length'] : response['Content-Length']
18
+ @logger.send @level, "[#{TAG_NAME}] [#{current_time}] #{response.code} \"#{http_method} #{path}\" #{content_length || '-'} "
19
19
  end
20
20
  end
21
21
  end
@@ -0,0 +1,91 @@
1
+ module HTTParty
2
+ module Logger
3
+ class CurlFormatter #:nodoc:
4
+ TAG_NAME = HTTParty.name
5
+ OUT = '>'.freeze
6
+ IN = '<'.freeze
7
+
8
+ attr_accessor :level, :logger
9
+
10
+ def initialize(logger, level)
11
+ @logger = logger
12
+ @level = level.to_sym
13
+ @messages = []
14
+ end
15
+
16
+ def format(request, response)
17
+ @request = request
18
+ @response = response
19
+
20
+ log_request
21
+ log_response
22
+
23
+ logger.send level, messages.join("\n")
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :request, :response
29
+ attr_accessor :messages
30
+
31
+ def log_request
32
+ log_url
33
+ log_headers
34
+ log_query
35
+ log OUT, request.raw_body if request.raw_body
36
+ log OUT
37
+ end
38
+
39
+ def log_response
40
+ log IN, "HTTP/#{response.http_version} #{response.code}"
41
+ log_response_headers
42
+ log IN, "\n#{response.body}"
43
+ log IN
44
+ end
45
+
46
+ def log_url
47
+ http_method = request.http_method.name.split("::").last.upcase
48
+ uri = if request.options[:base_uri]
49
+ request.options[:base_uri] + request.path.path
50
+ else
51
+ request.path.to_s
52
+ end
53
+
54
+ log OUT, "#{http_method} #{uri}"
55
+ end
56
+
57
+ def log_headers
58
+ return unless request.options[:headers] && request.options[:headers].size > 0
59
+
60
+ log OUT, 'Headers: '
61
+ log_hash request.options[:headers]
62
+ end
63
+
64
+ def log_query
65
+ return unless request.options[:query]
66
+
67
+ log OUT, 'Query: '
68
+ log_hash request.options[:query]
69
+ end
70
+
71
+ def log_response_headers
72
+ headers = response.respond_to?(:headers) ? response.headers : response
73
+ response.each_header do |response_header|
74
+ log IN, "#{response_header.capitalize}: #{headers[response_header]}"
75
+ end
76
+ end
77
+
78
+ def log_hash(hash)
79
+ hash.each { |k, v| log(OUT, "#{k}: #{v}") }
80
+ end
81
+
82
+ def log(direction, line = '')
83
+ messages << "[#{TAG_NAME}] [#{time}] #{direction} #{line}"
84
+ end
85
+
86
+ def time
87
+ @time ||= Time.now.strftime("%Y-%m-%d %H:%M:%S %z")
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,18 +1,26 @@
1
- require 'httparty/logger/apache_logger'
2
- require 'httparty/logger/curl_logger'
1
+ require 'httparty/logger/apache_formatter'
2
+ require 'httparty/logger/curl_formatter'
3
3
 
4
4
  module HTTParty
5
5
  module Logger
6
+ def self.formatters
7
+ @formatters ||= {
8
+ :curl => Logger::CurlFormatter,
9
+ :apache => Logger::ApacheFormatter
10
+ }
11
+ end
12
+
13
+ def self.add_formatter(name, formatter)
14
+ raise HTTParty::Error.new("Log Formatter with name #{name} already exists") if formatters.include?(name)
15
+ formatters.merge!(name.to_sym => formatter)
16
+ end
17
+
6
18
  def self.build(logger, level, formatter)
7
- level ||= :info
8
- format ||= :apache
19
+ level ||= :info
20
+ formatter ||= :apache
9
21
 
10
- case formatter
11
- when :curl
12
- Logger::CurlLogger.new(logger, level)
13
- else
14
- Logger::ApacheLogger.new(logger, level)
15
- end
22
+ logger_klass = formatters[formatter] || Logger::ApacheFormatter
23
+ logger_klass.new(logger, level)
16
24
  end
17
25
  end
18
26
  end