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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +92 -0
- data/.rubocop_todo.yml +124 -0
- data/.simplecov +1 -0
- data/.travis.yml +4 -2
- data/CONTRIBUTING.md +23 -0
- data/Gemfile +8 -3
- data/Guardfile +3 -3
- data/History +106 -11
- data/README.md +19 -20
- data/Rakefile +5 -7
- data/bin/httparty +18 -14
- data/docs/README.md +100 -0
- data/examples/README.md +67 -0
- data/examples/aaws.rb +5 -5
- data/examples/basic.rb +6 -10
- data/examples/crack.rb +2 -2
- data/examples/custom_parsers.rb +1 -4
- data/examples/delicious.rb +8 -8
- data/examples/google.rb +2 -2
- data/examples/headers_and_user_agents.rb +1 -1
- data/examples/logging.rb +36 -0
- data/examples/nokogiri_html_parser.rb +0 -3
- data/examples/rescue_json.rb +17 -0
- data/examples/rubyurl.rb +3 -3
- data/examples/stackexchange.rb +24 -0
- data/examples/tripit_sign_in.rb +20 -9
- data/examples/twitter.rb +7 -7
- data/examples/whoismyrep.rb +1 -1
- data/features/command_line.feature +90 -2
- data/features/digest_authentication.feature +10 -0
- data/features/steps/env.rb +16 -11
- data/features/steps/httparty_response_steps.rb +18 -14
- data/features/steps/httparty_steps.rb +10 -2
- data/features/steps/mongrel_helper.rb +35 -2
- data/features/steps/remote_service_steps.rb +26 -8
- data/features/supports_read_timeout_option.feature +13 -0
- data/httparty.gemspec +6 -5
- data/lib/httparty/connection_adapter.rb +36 -13
- data/lib/httparty/cookie_hash.rb +3 -4
- data/lib/httparty/exceptions.rb +4 -1
- data/lib/httparty/hash_conversions.rb +17 -15
- data/lib/httparty/logger/{apache_logger.rb → apache_formatter.rb} +3 -3
- data/lib/httparty/logger/curl_formatter.rb +91 -0
- data/lib/httparty/logger/logger.rb +18 -10
- data/lib/httparty/module_inheritable_attributes.rb +1 -1
- data/lib/httparty/net_digest_auth.rb +69 -18
- data/lib/httparty/parser.rb +4 -2
- data/lib/httparty/request.rb +105 -48
- data/lib/httparty/response.rb +31 -6
- data/lib/httparty/version.rb +1 -1
- data/lib/httparty.rb +132 -72
- data/spec/httparty/connection_adapter_spec.rb +285 -88
- data/spec/httparty/cookie_hash_spec.rb +46 -29
- data/spec/httparty/exception_spec.rb +29 -7
- data/spec/httparty/hash_conversions_spec.rb +49 -0
- data/spec/httparty/logger/apache_formatter_spec.rb +41 -0
- data/spec/httparty/logger/curl_formatter_spec.rb +119 -0
- data/spec/httparty/logger/logger_spec.rb +23 -7
- data/spec/httparty/net_digest_auth_spec.rb +118 -30
- data/spec/httparty/parser_spec.rb +43 -35
- data/spec/httparty/request_spec.rb +734 -182
- data/spec/httparty/response_spec.rb +139 -69
- data/spec/httparty/ssl_spec.rb +22 -22
- data/spec/httparty_spec.rb +307 -199
- data/spec/spec_helper.rb +34 -12
- data/spec/support/ssl_test_helper.rb +6 -6
- data/spec/support/ssl_test_server.rb +21 -21
- data/spec/support/stub_response.rb +20 -14
- data/website/index.html +3 -3
- metadata +30 -33
- data/lib/httparty/core_extensions.rb +0 -32
- data/lib/httparty/logger/curl_logger.rb +0 -48
- data/spec/httparty/logger/apache_logger_spec.rb +0 -26
- data/spec/httparty/logger/curl_logger_spec.rb +0 -18
- data/spec/spec.opts +0 -2
data/features/steps/env.rb
CHANGED
@@ -1,22 +1,27 @@
|
|
1
1
|
require 'mongrel'
|
2
2
|
require './lib/httparty'
|
3
|
-
require '
|
3
|
+
require 'rspec/expectations'
|
4
|
+
require 'aruba/cucumber'
|
4
5
|
|
5
|
-
|
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.
|
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.
|
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.
|
24
|
-
@response_from_httparty.keys.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.
|
28
|
-
@response_from_httparty[key].
|
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.
|
34
|
-
@response_from_httparty.
|
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.
|
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'].
|
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.
|
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.
|
51
|
-
@exception_from_httparty.
|
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
|
-
:
|
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
|
-
:
|
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
|
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
|
-
|
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+)
|
21
|
-
|
22
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
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 =
|
13
|
-
s.description =
|
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
|
-
#
|
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
|
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
|
-
|
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
|
-
|
70
|
-
|
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,
|
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
|
-
|
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.
|
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
|
data/lib/httparty/cookie_hash.rb
CHANGED
@@ -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
|
-
|
19
|
+
reject { |k, v| CLIENT_COOKIES.include?(k.to_s.downcase) }.collect { |k, v| "#{k}=#{v}" }.join("; ")
|
21
20
|
end
|
22
21
|
end
|
data/lib/httparty/exceptions.rb
CHANGED
@@ -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-
|
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
|
-
# { :
|
7
|
-
# :
|
8
|
-
# :
|
9
|
-
# :
|
10
|
-
# :
|
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
|
-
|
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.
|
31
|
-
param << value.
|
32
|
-
|
33
|
-
|
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}=#{
|
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.
|
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
|
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/
|
2
|
-
require 'httparty/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
|
8
|
-
|
19
|
+
level ||= :info
|
20
|
+
formatter ||= :apache
|
9
21
|
|
10
|
-
|
11
|
-
|
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
|