httparty 0.10.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 +7 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +92 -0
- data/.rubocop_todo.yml +124 -0
- data/.simplecov +1 -0
- data/.travis.yml +5 -4
- data/CONTRIBUTING.md +23 -0
- data/Gemfile +9 -5
- data/Guardfile +3 -3
- data/History +109 -8
- data/README.md +21 -21
- data/Rakefile +5 -10
- data/bin/httparty +21 -14
- data/docs/README.md +100 -0
- data/examples/README.md +67 -0
- data/examples/aaws.rb +9 -9
- data/examples/basic.rb +6 -10
- data/examples/crack.rb +3 -3
- data/examples/custom_parsers.rb +1 -4
- data/examples/delicious.rb +12 -12
- data/examples/google.rb +2 -2
- data/examples/headers_and_user_agents.rb +2 -2
- 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 +11 -11
- data/examples/whoismyrep.rb +2 -2
- data/features/command_line.feature +90 -2
- data/features/digest_authentication.feature +10 -0
- data/features/handles_compressed_responses.feature +8 -0
- data/features/handles_multiple_formats.feature +23 -0
- data/features/steps/env.rb +16 -11
- data/features/steps/httparty_response_steps.rb +40 -10
- data/features/steps/httparty_steps.rb +19 -3
- data/features/steps/mongrel_helper.rb +35 -2
- data/features/steps/remote_service_steps.rb +31 -8
- data/features/supports_read_timeout_option.feature +13 -0
- data/httparty.gemspec +9 -6
- data/lib/httparty/connection_adapter.rb +76 -11
- data/lib/httparty/cookie_hash.rb +3 -4
- data/lib/httparty/exceptions.rb +10 -4
- data/lib/httparty/hash_conversions.rb +19 -17
- data/lib/httparty/logger/apache_formatter.rb +22 -0
- data/lib/httparty/logger/curl_formatter.rb +91 -0
- data/lib/httparty/logger/logger.rb +26 -0
- data/lib/httparty/module_inheritable_attributes.rb +1 -1
- data/lib/httparty/net_digest_auth.rb +69 -18
- data/lib/httparty/parser.rb +15 -11
- data/lib/httparty/request.rb +186 -47
- data/lib/httparty/response/headers.rb +2 -2
- data/lib/httparty/response.rb +44 -9
- data/lib/httparty/version.rb +1 -1
- data/lib/httparty.rb +187 -65
- data/script/release +42 -0
- data/spec/fixtures/twitter.csv +2 -0
- data/spec/httparty/connection_adapter_spec.rb +334 -62
- data/spec/httparty/cookie_hash_spec.rb +53 -23
- data/spec/httparty/exception_spec.rb +45 -0
- 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 +38 -0
- data/spec/httparty/net_digest_auth_spec.rb +148 -23
- data/spec/httparty/parser_spec.rb +48 -41
- data/spec/httparty/request_spec.rb +845 -151
- data/spec/httparty/response_spec.rb +147 -70
- data/spec/httparty/ssl_spec.rb +33 -21
- data/spec/httparty_spec.rb +337 -186
- data/spec/spec_helper.rb +38 -9
- data/spec/support/ssl_test_helper.rb +10 -10
- data/spec/support/ssl_test_server.rb +21 -21
- data/spec/support/stub_response.rb +20 -14
- data/website/index.html +3 -3
- metadata +46 -37
- data/lib/httparty/core_extensions.rb +0 -32
- data/spec/spec.opts +0 -2
@@ -18,3 +18,13 @@ Feature: Digest Authentication
|
|
18
18
|
| username | password |
|
19
19
|
| jcash | maninblack |
|
20
20
|
Then the return value should match 'Digest Authenticated Page'
|
21
|
+
|
22
|
+
Scenario: Passing proper credentials to a page requiring Digest Authentication using md5-sess algorithm
|
23
|
+
Given a remote service that returns 'Digest Authenticated Page Using MD5-sess'
|
24
|
+
And that service is accessed at the path '/digest_auth.html'
|
25
|
+
And that service is protected by MD5-sess Digest Authentication
|
26
|
+
And that service requires the username 'jcash' with the password 'maninblack'
|
27
|
+
When I call HTTParty#get with '/digest_auth.html' and a digest_auth hash:
|
28
|
+
| username | password |
|
29
|
+
| jcash | maninblack |
|
30
|
+
Then the return value should match 'Digest Authenticated Page Using MD5-sess'
|
@@ -17,3 +17,11 @@ Feature: Handles Compressed Responses
|
|
17
17
|
And that service is accessed at the path '/gzip_service.html'
|
18
18
|
When I call HTTParty#get with '/gzip_service.html'
|
19
19
|
Then the return value should match '<h1>Some HTML</h1>'
|
20
|
+
|
21
|
+
Scenario: Supports HEAD request with gzip encoding
|
22
|
+
Given a remote gzip service
|
23
|
+
And that service is accessed at the path '/gzip_head.gz.js'
|
24
|
+
When I call HTTParty#head with '/gzip_head.gz.js'
|
25
|
+
Then it should return a response with a 200 response code
|
26
|
+
Then it should return a response with a gzip content-encoding
|
27
|
+
Then it should return a response with a blank body
|
@@ -14,6 +14,21 @@ Feature: Handles Multiple Formats
|
|
14
14
|
Then it should return a String
|
15
15
|
And the return value should match '<h1>Some HTML</h1>'
|
16
16
|
|
17
|
+
Scenario: A CSV service
|
18
|
+
Given a remote service that returns:
|
19
|
+
"""
|
20
|
+
"Last Name","Name"
|
21
|
+
"jennings","waylon"
|
22
|
+
"cash","johnny"
|
23
|
+
"""
|
24
|
+
And that service is accessed at the path '/service.csv'
|
25
|
+
And the response from the service has a Content-Type of 'application/csv'
|
26
|
+
When I call HTTParty#get with '/service.csv'
|
27
|
+
Then it should return an Array equaling:
|
28
|
+
| Last Name | Name |
|
29
|
+
| jennings | waylon |
|
30
|
+
| cash | johnny |
|
31
|
+
|
17
32
|
Scenario: A JSON service
|
18
33
|
Given a remote service that returns '{ "jennings": "waylon", "cash": "johnny" }'
|
19
34
|
And that service is accessed at the path '/service.json'
|
@@ -32,3 +47,11 @@ Feature: Handles Multiple Formats
|
|
32
47
|
Then it should return a Hash equaling:
|
33
48
|
| key | value |
|
34
49
|
| singer | waylon jennings |
|
50
|
+
|
51
|
+
Scenario: A Javascript remote file
|
52
|
+
Given a remote service that returns '$(function() { alert("hi"); });'
|
53
|
+
And that service is accessed at the path '/service.js'
|
54
|
+
And the response from the service has a Content-Type of 'application/javascript'
|
55
|
+
When I call HTTParty#get with '/service.js'
|
56
|
+
Then it should return a String
|
57
|
+
And the return value should match '$(function() { alert("hi"); });'
|
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
|
@@ -1,26 +1,56 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# Not needed anymore in ruby 2.0, but needed to resolve constants
|
2
|
+
# in nested namespaces. This is taken from rails :)
|
3
|
+
def constantize(camel_cased_word)
|
4
|
+
names = camel_cased_word.split('::')
|
5
|
+
names.shift if names.empty? || names.first.empty?
|
6
|
+
|
7
|
+
constant = Object
|
8
|
+
names.each do |name|
|
9
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
10
|
+
end
|
11
|
+
constant
|
12
|
+
end
|
13
|
+
|
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))
|
3
16
|
end
|
4
17
|
|
5
18
|
Then /the return value should match '(.*)'/ do |expected_text|
|
6
|
-
@response_from_httparty.
|
19
|
+
expect(@response_from_httparty.parsed_response).to eq(expected_text)
|
7
20
|
end
|
8
21
|
|
9
22
|
Then /it should return a Hash equaling:/ do |hash_table|
|
10
|
-
@response_from_httparty.
|
11
|
-
@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)
|
12
25
|
hash_table.hashes.each do |pair|
|
13
26
|
key, value = pair["key"], pair["value"]
|
14
|
-
@response_from_httparty.keys.
|
15
|
-
@response_from_httparty[key].
|
27
|
+
expect(@response_from_httparty.keys).to include(key)
|
28
|
+
expect(@response_from_httparty[key]).to eq(value)
|
16
29
|
end
|
17
30
|
end
|
18
31
|
|
32
|
+
Then /it should return an Array equaling:/ do |array|
|
33
|
+
expect(@response_from_httparty.parsed_response).to be_a(Array)
|
34
|
+
expect(@response_from_httparty.parsed_response).to eq(array.raw)
|
35
|
+
end
|
36
|
+
|
19
37
|
Then /it should return a response with a (\d+) response code/ do |code|
|
20
|
-
@response_from_httparty.code.
|
38
|
+
expect(@response_from_httparty.code).to eq(code.to_i)
|
39
|
+
end
|
40
|
+
|
41
|
+
Then /it should return a response with a (.*) content\-encoding$/ do |content_type|
|
42
|
+
expect(@response_from_httparty.headers['content-encoding']).to eq('gzip')
|
43
|
+
end
|
44
|
+
|
45
|
+
Then /it should return a response with a blank body$/ do
|
46
|
+
expect(@response_from_httparty.body).to be_nil
|
21
47
|
end
|
22
48
|
|
23
49
|
Then /it should raise (?:an|a) ([\w:]+) exception/ do |exception|
|
24
|
-
@exception_from_httparty.
|
25
|
-
@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
|
26
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)
|
@@ -10,11 +18,19 @@ When /I call HTTParty#get with '(.*)'$/ do |url|
|
|
10
18
|
end
|
11
19
|
end
|
12
20
|
|
21
|
+
When /^I call HTTParty#head with '(.*)'$/ do |url|
|
22
|
+
begin
|
23
|
+
@response_from_httparty = HTTParty.head("http://#{@host_and_port}#{url}", @request_options)
|
24
|
+
rescue HTTParty::RedirectionTooDeep, Timeout::Error => e
|
25
|
+
@exception_from_httparty = e
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
13
29
|
When /I call HTTParty#get with '(.*)' and a basic_auth hash:/ do |url, auth_table|
|
14
30
|
h = auth_table.hashes.first
|
15
31
|
@response_from_httparty = HTTParty.get(
|
16
32
|
"http://#{@host_and_port}#{url}",
|
17
|
-
:
|
33
|
+
basic_auth: { username: h["username"], password: h["password"] }
|
18
34
|
)
|
19
35
|
end
|
20
36
|
|
@@ -22,6 +38,6 @@ When /I call HTTParty#get with '(.*)' and a digest_auth hash:/ do |url, auth_tab
|
|
22
38
|
h = auth_table.hashes.first
|
23
39
|
@response_from_httparty = HTTParty.get(
|
24
40
|
"http://#{@host_and_port}#{url}",
|
25
|
-
:
|
41
|
+
digest_auth: { username: h["username"], password: h["password"] }
|
26
42
|
)
|
27
|
-
end
|
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,11 @@
|
|
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
|
+
end
|
5
|
+
|
6
|
+
Given /^a remote service that returns:$/ do |response_body|
|
7
|
+
@handler = BasicMongrelHandler.new
|
8
|
+
@handler.response_body = response_body
|
4
9
|
end
|
5
10
|
|
6
11
|
Given /a remote service that returns a (\d+) status code/ do |code|
|
@@ -12,15 +17,22 @@ Given /that service is accessed at the path '(.*)'/ do |path|
|
|
12
17
|
@server.register(path, @handler)
|
13
18
|
end
|
14
19
|
|
15
|
-
Given /^that service takes (\d+)
|
16
|
-
|
17
|
-
|
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 }
|
18
25
|
end
|
19
26
|
|
20
27
|
Given /^a remote deflate service$/ do
|
21
28
|
@handler = DeflateHandler.new
|
22
29
|
end
|
23
30
|
|
31
|
+
Given /^a remote deflate service on port '(\d+)'/ do |port|
|
32
|
+
run_server(port)
|
33
|
+
@handler = DeflateHandler.new
|
34
|
+
end
|
35
|
+
|
24
36
|
Given /^a remote gzip service$/ do
|
25
37
|
@handler = GzipHandler.new
|
26
38
|
end
|
@@ -45,16 +57,27 @@ Given /that service is protected by Digest Authentication/ do
|
|
45
57
|
@handler.extend DigestAuthentication
|
46
58
|
end
|
47
59
|
|
60
|
+
Given /that service is protected by MD5-sess Digest Authentication/ do
|
61
|
+
@handler.extend DigestAuthenticationUsingMD5Sess
|
62
|
+
end
|
63
|
+
|
48
64
|
Given /that service requires the username '(.*)' with the password '(.*)'/ do |username, password|
|
49
65
|
@handler.username = username
|
50
66
|
@handler.password = password
|
51
67
|
end
|
52
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
|
+
|
53
74
|
Given /a restricted page at '(.*)'/ do |url|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
+
"
|
58
81
|
end
|
59
82
|
|
60
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,24 +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
|
-
s.
|
16
|
-
s.add_dependency 'multi_xml'
|
16
|
+
s.required_ruby_version = '>= 1.9.3'
|
17
17
|
|
18
|
+
s.add_dependency 'multi_xml', ">= 0.5.2"
|
19
|
+
|
20
|
+
# If this line is removed, all hard partying will cease.
|
18
21
|
s.post_install_message = "When you HTTParty, you must party hard!"
|
19
22
|
|
20
23
|
s.files = `git ls-files`.split("\n")
|
21
24
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
-
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) }
|
23
26
|
s.require_paths = ["lib"]
|
24
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,16 +65,22 @@ 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
|
-
|
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])
|
81
|
+
else
|
82
|
+
http = Net::HTTP.new(host, port)
|
83
|
+
end
|
70
84
|
|
71
85
|
http.use_ssl = ssl_implied?(uri)
|
72
86
|
|
@@ -77,6 +91,14 @@ module HTTParty
|
|
77
91
|
http.read_timeout = options[:timeout]
|
78
92
|
end
|
79
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
|
+
|
80
102
|
if options[:debug_output]
|
81
103
|
http.set_debug_output(options[:debug_output])
|
82
104
|
end
|
@@ -85,7 +107,26 @@ module HTTParty
|
|
85
107
|
http.ciphers = options[:ciphers]
|
86
108
|
end
|
87
109
|
|
88
|
-
|
110
|
+
# Bind to a specific local address or port
|
111
|
+
#
|
112
|
+
# @see https://bugs.ruby-lang.org/issues/6617
|
113
|
+
if options[:local_host]
|
114
|
+
if RUBY_VERSION >= "2.0.0"
|
115
|
+
http.local_host = options[:local_host]
|
116
|
+
else
|
117
|
+
Kernel.warn("Warning: option :local_host requires Ruby version 2.0 or later")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if options[:local_port]
|
122
|
+
if RUBY_VERSION >= "2.0.0"
|
123
|
+
http.local_port = options[:local_port]
|
124
|
+
else
|
125
|
+
Kernel.warn("Warning: option :local_port requires Ruby version 2.0 or later")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
http
|
89
130
|
end
|
90
131
|
|
91
132
|
private
|
@@ -99,18 +140,42 @@ module HTTParty
|
|
99
140
|
end
|
100
141
|
|
101
142
|
def ssl_implied?(uri)
|
102
|
-
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)
|
103
148
|
end
|
104
149
|
|
105
150
|
def attach_ssl_certificates(http, options)
|
106
151
|
if http.use_ssl?
|
107
|
-
|
152
|
+
if options.fetch(:verify, true)
|
153
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
154
|
+
if options[:cert_store]
|
155
|
+
http.cert_store = options[:cert_store]
|
156
|
+
else
|
157
|
+
# Use the default cert store by default, i.e. system ca certs
|
158
|
+
http.cert_store = OpenSSL::X509::Store.new
|
159
|
+
http.cert_store.set_default_paths
|
160
|
+
end
|
161
|
+
else
|
162
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
163
|
+
end
|
108
164
|
|
109
165
|
# Client certificate authentication
|
166
|
+
# Note: options[:pem] must contain the content of a PEM file having the private key appended
|
110
167
|
if options[:pem]
|
111
168
|
http.cert = OpenSSL::X509::Certificate.new(options[:pem])
|
112
169
|
http.key = OpenSSL::PKey::RSA.new(options[:pem], options[:pem_password])
|
113
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
170
|
+
http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
171
|
+
end
|
172
|
+
|
173
|
+
# PKCS12 client certificate authentication
|
174
|
+
if options[:p12]
|
175
|
+
p12 = OpenSSL::PKCS12.new(options[:p12], options[:p12_password])
|
176
|
+
http.cert = p12.certificate
|
177
|
+
http.key = p12.key
|
178
|
+
http.verify_mode = verify_ssl_certificate? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
114
179
|
end
|
115
180
|
|
116
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('=')
|
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
@@ -1,13 +1,16 @@
|
|
1
1
|
module HTTParty
|
2
|
-
#
|
3
|
-
class
|
2
|
+
# @abstact Exceptions raised by HTTParty inherit from Error
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
# Exception raised when you attempt to set a non-existent format
|
6
|
+
class UnsupportedFormat < Error; end
|
4
7
|
|
5
8
|
# Exception raised when using a URI scheme other than HTTP or HTTPS
|
6
|
-
class UnsupportedURIScheme <
|
9
|
+
class UnsupportedURIScheme < Error; end
|
7
10
|
|
8
11
|
# @abstract Exceptions which inherit from ResponseError contain the Net::HTTP
|
9
12
|
# response object accessible via the {#response} method.
|
10
|
-
class ResponseError <
|
13
|
+
class ResponseError < Error
|
11
14
|
# Returns the response of the last request
|
12
15
|
# @return [Net::HTTPResponse] A subclass of Net::HTTPResponse, e.g.
|
13
16
|
# Net::HTTPOK
|
@@ -23,4 +26,7 @@ module HTTParty
|
|
23
26
|
# Exception that is raised when request has redirected too many times.
|
24
27
|
# Calling {#response} returns the Net:HTTP response object.
|
25
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
|
26
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,20 +25,24 @@ 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
|
-
hash.each do |
|
40
|
-
if
|
41
|
-
stack << ["#{parent}[#{
|
41
|
+
hash.each do |k, v|
|
42
|
+
if v.respond_to?(:to_hash)
|
43
|
+
stack << ["#{parent}[#{k}]", v.to_hash]
|
42
44
|
else
|
43
|
-
param << normalize_param("#{parent}[#{
|
45
|
+
param << normalize_param("#{parent}[#{k}]", v)
|
44
46
|
end
|
45
47
|
end
|
46
48
|
end
|