httparty 0.14.0 → 0.15.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/.travis.yml +0 -1
- data/{History → History.md} +85 -63
- data/README.md +3 -3
- data/bin/httparty +3 -1
- data/docs/README.md +9 -0
- data/examples/README.md +6 -1
- data/examples/aaws.rb +1 -1
- data/examples/crack.rb +1 -1
- data/examples/custom_parsers.rb +1 -1
- data/examples/delicious.rb +4 -4
- data/examples/logging.rb +1 -1
- data/examples/stackexchange.rb +1 -1
- data/examples/stream_download.rb +20 -0
- data/examples/tripit_sign_in.rb +1 -1
- data/examples/twitter.rb +2 -2
- data/examples/whoismyrep.rb +1 -1
- data/httparty.gemspec +1 -1
- data/lib/httparty.rb +10 -7
- data/lib/httparty/connection_adapter.rb +16 -2
- data/lib/httparty/hash_conversions.rb +2 -0
- data/lib/httparty/net_digest_auth.rb +6 -6
- data/lib/httparty/parser.rb +3 -1
- data/lib/httparty/request.rb +76 -36
- data/lib/httparty/response.rb +26 -18
- data/lib/httparty/response/headers.rb +19 -19
- data/lib/httparty/version.rb +1 -1
- data/spec/fixtures/ssl/generated/1fe462c2.0 +1 -0
- data/spec/httparty/net_digest_auth_spec.rb +28 -0
- data/spec/httparty/parser_spec.rb +5 -0
- data/spec/httparty/request_spec.rb +158 -97
- data/spec/httparty/response_spec.rb +69 -13
- data/spec/httparty_spec.rb +7 -2
- metadata +6 -5
- data/spec/fixtures/ssl/generated/1fe462c2.0 +0 -16
data/bin/httparty
CHANGED
@@ -103,9 +103,11 @@ else
|
|
103
103
|
when :json
|
104
104
|
begin
|
105
105
|
require 'json'
|
106
|
-
puts JSON.pretty_generate(response)
|
106
|
+
puts JSON.pretty_generate(response.parsed_response)
|
107
107
|
rescue LoadError
|
108
108
|
puts YAML.dump(response)
|
109
|
+
rescue JSON::JSONError
|
110
|
+
puts response.inspect
|
109
111
|
end
|
110
112
|
when :xml
|
111
113
|
require 'rexml/document'
|
data/docs/README.md
CHANGED
@@ -3,8 +3,17 @@
|
|
3
3
|
Makes http fun again!
|
4
4
|
|
5
5
|
## Table of contents
|
6
|
+
- [Parsing JSON](#parsing-json)
|
6
7
|
- [Working with SSL](#working-with-ssl)
|
7
8
|
|
9
|
+
## Parsing JSON
|
10
|
+
If the response Content Type is `application/json`, HTTParty will parse the response and return Ruby objects such as a hash or array. The default behavior for parsing JSON will return keys as strings. This can be supressed with the `format` option. To get hash keys as symbols:
|
11
|
+
|
12
|
+
```
|
13
|
+
response = HTTParty.get('http://example.com', format: :plain)
|
14
|
+
JSON.parse response, symbolize_names: true
|
15
|
+
```
|
16
|
+
|
8
17
|
## Working with SSL
|
9
18
|
|
10
19
|
You can use this guide to work with SSL certificates.
|
data/examples/README.md
CHANGED
@@ -64,4 +64,9 @@
|
|
64
64
|
* Two ways to pass params to get, inline on the url or in query hash
|
65
65
|
|
66
66
|
* [Rescue Json Error](rescue_json.rb)
|
67
|
-
* Rescue errors due to parsing response
|
67
|
+
* Rescue errors due to parsing response
|
68
|
+
|
69
|
+
* [Download file using stream mode](stream_download.rb)
|
70
|
+
* Uses `get` requests
|
71
|
+
* Uses `stream_body` mode
|
72
|
+
* Download file without using the memory
|
data/examples/aaws.rb
CHANGED
data/examples/crack.rb
CHANGED
@@ -16,4 +16,4 @@ class Rep
|
|
16
16
|
end
|
17
17
|
|
18
18
|
pp Rep.get('http://whoismyrepresentative.com/getall_mems.php?zip=46544')
|
19
|
-
pp Rep.get('http://whoismyrepresentative.com/getall_mems.php', query: {zip: 46544})
|
19
|
+
pp Rep.get('http://whoismyrepresentative.com/getall_mems.php', query: { zip: 46544 })
|
data/examples/custom_parsers.rb
CHANGED
data/examples/delicious.rb
CHANGED
@@ -8,7 +8,7 @@ class Delicious
|
|
8
8
|
base_uri 'https://api.del.icio.us/v1'
|
9
9
|
|
10
10
|
def initialize(u, p)
|
11
|
-
@auth = {username: u, password: p}
|
11
|
+
@auth = { username: u, password: p }
|
12
12
|
end
|
13
13
|
|
14
14
|
# query params that filter the posts are:
|
@@ -17,7 +17,7 @@ class Delicious
|
|
17
17
|
# url (optional). Filter by this url.
|
18
18
|
# ie: posts(query: {tag: 'ruby'})
|
19
19
|
def posts(options = {})
|
20
|
-
options.merge!({basic_auth: @auth})
|
20
|
+
options.merge!({ basic_auth: @auth })
|
21
21
|
self.class.get('/posts/get', options)
|
22
22
|
end
|
23
23
|
|
@@ -25,13 +25,13 @@ class Delicious
|
|
25
25
|
# tag (optional). Filter by this tag.
|
26
26
|
# count (optional). Number of items to retrieve (Default:15, Maximum:100).
|
27
27
|
def recent(options = {})
|
28
|
-
options.merge!({basic_auth: @auth})
|
28
|
+
options.merge!({ basic_auth: @auth })
|
29
29
|
self.class.get('/posts/recent', options)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
delicious = Delicious.new(config['username'], config['password'])
|
34
|
-
pp delicious.posts(query: {tag: 'ruby'})
|
34
|
+
pp delicious.posts(query: { tag: 'ruby' })
|
35
35
|
pp delicious.recent
|
36
36
|
|
37
37
|
delicious.recent['posts']['post'].each { |post| puts post['href'] }
|
data/examples/logging.rb
CHANGED
@@ -22,7 +22,7 @@ Google.get "http://google.com"
|
|
22
22
|
my_logger.info '*' * 70
|
23
23
|
|
24
24
|
my_logger.info "The default formatter is :apache. The :curl formatter can also be used."
|
25
|
-
my_logger.info "You can tell
|
25
|
+
my_logger.info "You can tell which method to call on the logger too. It is info by default."
|
26
26
|
HTTParty.get "http://google.com", logger: my_logger, log_level: :debug, log_format: :curl
|
27
27
|
|
28
28
|
my_logger.info '*' * 70
|
data/examples/stackexchange.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require File.join(dir, 'httparty')
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
# download file linux-4.6.4.tar.xz without using the memory
|
6
|
+
response = nil
|
7
|
+
filename = "linux-4.6.4.tar.xz"
|
8
|
+
url = "https://cdn.kernel.org/pub/linux/kernel/v4.x/#{filename}"
|
9
|
+
|
10
|
+
File.open(filename, "w") do |file|
|
11
|
+
response = HTTParty.get(url, stream_body: true) do |fragment|
|
12
|
+
print "."
|
13
|
+
file.write(fragment)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
puts
|
17
|
+
|
18
|
+
pp "Success: #{response.success?}"
|
19
|
+
pp File.stat(filename).inspect
|
20
|
+
File.unlink(filename)
|
data/examples/tripit_sign_in.rb
CHANGED
data/examples/twitter.rb
CHANGED
@@ -14,12 +14,12 @@ class Twitter
|
|
14
14
|
# which can be :friends, :user or :public
|
15
15
|
# options[:query] can be things like since, since_id, count, etc.
|
16
16
|
def timeline(which = :friends, options = {})
|
17
|
-
options.merge!({basic_auth: @auth})
|
17
|
+
options.merge!({ basic_auth: @auth })
|
18
18
|
self.class.get("/statuses/#{which}_timeline.json", options)
|
19
19
|
end
|
20
20
|
|
21
21
|
def post(text)
|
22
|
-
options = { query: {status: text}, basic_auth: @auth }
|
22
|
+
options = { query: { status: text }, basic_auth: @auth }
|
23
23
|
self.class.post('/statuses/update.json', options)
|
24
24
|
end
|
25
25
|
end
|
data/examples/whoismyrep.rb
CHANGED
data/httparty.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.summary = 'Makes http fun! Also, makes consuming restful web services dead easy.'
|
14
14
|
s.description = 'Makes http fun! Also, makes consuming restful web services dead easy.'
|
15
15
|
|
16
|
-
s.required_ruby_version = '>=
|
16
|
+
s.required_ruby_version = '>= 2.0.0'
|
17
17
|
|
18
18
|
s.add_dependency 'multi_xml', ">= 0.5.2"
|
19
19
|
|
data/lib/httparty.rb
CHANGED
@@ -6,7 +6,6 @@ require 'zlib'
|
|
6
6
|
require 'multi_xml'
|
7
7
|
require 'json'
|
8
8
|
require 'csv'
|
9
|
-
require 'erb'
|
10
9
|
|
11
10
|
require 'httparty/module_inheritable_attributes'
|
12
11
|
require 'httparty/cookie_hash'
|
@@ -39,7 +38,7 @@ module HTTParty
|
|
39
38
|
# [:+timeout+:] Timeout for opening connection and reading data.
|
40
39
|
# [:+local_host:] Local address to bind to before connecting.
|
41
40
|
# [:+local_port:] Local port to bind to before connecting.
|
42
|
-
# [:+
|
41
|
+
# [:+body_stream:] Allow streaming to a REST server to specify a body_stream.
|
43
42
|
# [:+stream_body:] Allow for streaming large files without loading them into memory.
|
44
43
|
#
|
45
44
|
# There are also another set of options with names corresponding to various class methods. The methods in question are those that let you set a class-wide default, and the options override the defaults on a request-by-request basis. Those options are:
|
@@ -128,7 +127,7 @@ module HTTParty
|
|
128
127
|
end
|
129
128
|
|
130
129
|
# Do not send rails style query strings.
|
131
|
-
#
|
130
|
+
# Specifically, don't use bracket notation when sending an array
|
132
131
|
#
|
133
132
|
# For a query:
|
134
133
|
# get '/', query: {selected_ids: [1,2,3]}
|
@@ -213,10 +212,14 @@ module HTTParty
|
|
213
212
|
# include HTTParty
|
214
213
|
# headers 'Accept' => 'text/html'
|
215
214
|
# end
|
216
|
-
def headers(h =
|
217
|
-
|
218
|
-
|
219
|
-
|
215
|
+
def headers(h = nil)
|
216
|
+
if h
|
217
|
+
raise ArgumentError, 'Headers must be an object which responds to #to_hash' unless h.respond_to?(:to_hash)
|
218
|
+
default_options[:headers] ||= {}
|
219
|
+
default_options[:headers].merge!(h.to_hash)
|
220
|
+
else
|
221
|
+
default_options[:headers] || {}
|
222
|
+
end
|
220
223
|
end
|
221
224
|
|
222
225
|
def cookies(h = {})
|
@@ -38,17 +38,31 @@ 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
|
+
# The keys used in options are
|
42
42
|
# * :+timeout+: timeout in seconds
|
43
43
|
# * :+open_timeout+: http connection open_timeout in seconds, overrides timeout if set
|
44
44
|
# * :+read_timeout+: http connection read_timeout in seconds, overrides timeout if set
|
45
45
|
# * :+debug_output+: see HTTParty::ClassMethods.debug_output.
|
46
|
-
# * :+
|
46
|
+
# * :+cert_store+: contains certificate data. see method 'attach_ssl_certificates'
|
47
|
+
# * :+pem+: contains pem client certificate data. see method 'attach_ssl_certificates'
|
48
|
+
# * :+p12+: contains PKCS12 client client certificate data. see method 'attach_ssl_certificates'
|
47
49
|
# * :+verify+: verify the server’s certificate against the ca certificate.
|
48
50
|
# * :+verify_peer+: set to false to turn off server verification but still send client certificate
|
49
51
|
# * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file.
|
50
52
|
# * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path.
|
53
|
+
# * :+ssl_version+: SSL versions to allow. see method 'attach_ssl_certificates'
|
54
|
+
# * :+ciphers+: The list of SSL ciphers to support
|
51
55
|
# * :+connection_adapter_options+: contains the hash you passed to HTTParty.connection_adapter when you configured your connection adapter
|
56
|
+
# * :+local_host+: The local address to bind to
|
57
|
+
# * :+local_port+: The local port to bind to
|
58
|
+
# * :+http_proxyaddr+: HTTP Proxy address
|
59
|
+
# * :+http_proxyport+: HTTP Proxy port
|
60
|
+
# * :+http_proxyuser+: HTTP Proxy user
|
61
|
+
# * :+http_proxypass+: HTTP Proxy password
|
62
|
+
#
|
63
|
+
# === Inherited methods
|
64
|
+
# * :+clean_host+: Method used to sanitize host names
|
65
|
+
|
52
66
|
class ConnectionAdapter
|
53
67
|
# Private: Regex used to strip brackets from IPv6 URIs.
|
54
68
|
StripIpv6BracketsRegex = /\A\[(.*)\]\z/
|
@@ -12,13 +12,13 @@ module Net
|
|
12
12
|
response
|
13
13
|
)
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
authenticator.authorization_header.each do |v|
|
16
|
+
add_field('Authorization', v)
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
authenticator.cookie_header.each do |v|
|
20
|
+
add_field('Cookie', v)
|
21
|
+
end
|
22
22
|
end
|
23
23
|
|
24
24
|
class DigestAuthenticator
|
data/lib/httparty/parser.rb
CHANGED
@@ -114,8 +114,10 @@ module HTTParty
|
|
114
114
|
MultiXml.parse(body)
|
115
115
|
end
|
116
116
|
|
117
|
+
UTF8_BOM = "\xEF\xBB\xBF".freeze
|
118
|
+
|
117
119
|
def json
|
118
|
-
JSON.parse(body, :quirks_mode => true, :allow_nan => true)
|
120
|
+
JSON.parse(body.gsub(/\A#{UTF8_BOM}/, ''), :quirks_mode => true, :allow_nan => true)
|
119
121
|
end
|
120
122
|
|
121
123
|
def csv
|
data/lib/httparty/request.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
1
3
|
module HTTParty
|
2
4
|
class Request #:nodoc:
|
3
5
|
SupportedHTTPMethods = [
|
@@ -27,10 +29,26 @@ module HTTParty
|
|
27
29
|
end.flatten.join('&')
|
28
30
|
end
|
29
31
|
|
32
|
+
JSON_API_QUERY_STRING_NORMALIZER = proc do |query|
|
33
|
+
Array(query).sort_by { |a| a[0].to_s }.map do |key, value|
|
34
|
+
if value.nil?
|
35
|
+
key.to_s
|
36
|
+
elsif value.respond_to?(:to_ary)
|
37
|
+
values = value.to_ary.map{|v| ERB::Util.url_encode(v.to_s)}
|
38
|
+
"#{key}=#{values.join(',')}"
|
39
|
+
else
|
40
|
+
HashConversions.to_params(key => value)
|
41
|
+
end
|
42
|
+
end.flatten.join('&')
|
43
|
+
end
|
44
|
+
|
30
45
|
attr_accessor :http_method, :options, :last_response, :redirect, :last_uri
|
31
46
|
attr_reader :path
|
32
47
|
|
33
48
|
def initialize(http_method, path, o = {})
|
49
|
+
@changed_hosts = false
|
50
|
+
@credentials_sent = false
|
51
|
+
|
34
52
|
self.http_method = http_method
|
35
53
|
self.options = {
|
36
54
|
limit: o.delete(:no_follow) ? 1 : 5,
|
@@ -42,7 +60,7 @@ module HTTParty
|
|
42
60
|
connection_adapter: ConnectionAdapter
|
43
61
|
}.merge(o)
|
44
62
|
self.path = path
|
45
|
-
set_basic_auth_from_uri
|
63
|
+
set_basic_auth_from_uri
|
46
64
|
end
|
47
65
|
|
48
66
|
def path=(uri)
|
@@ -74,7 +92,13 @@ module HTTParty
|
|
74
92
|
path.path = last_uri_host + path.path
|
75
93
|
end
|
76
94
|
|
77
|
-
|
95
|
+
if path.relative? && path.host
|
96
|
+
new_uri = options[:uri_adapter].parse("#{@last_uri.scheme}:#{path}")
|
97
|
+
elsif path.relative?
|
98
|
+
new_uri = options[:uri_adapter].parse("#{base_uri}#{path}")
|
99
|
+
else
|
100
|
+
new_uri = path.clone
|
101
|
+
end
|
78
102
|
|
79
103
|
# avoid double query string on redirects [#12]
|
80
104
|
unless redirect
|
@@ -127,10 +151,19 @@ module HTTParty
|
|
127
151
|
chunked_body = chunks.join
|
128
152
|
end
|
129
153
|
end
|
130
|
-
|
131
|
-
|
154
|
+
|
155
|
+
|
132
156
|
handle_host_redirection if response_redirects?
|
133
|
-
|
157
|
+
result = handle_unauthorized
|
158
|
+
result ||= handle_response(chunked_body, &block)
|
159
|
+
result
|
160
|
+
end
|
161
|
+
|
162
|
+
def handle_unauthorized(&block)
|
163
|
+
return unless digest_auth? && response_unauthorized? && response_has_digest_auth_challenge?
|
164
|
+
return if @credentials_sent
|
165
|
+
@credentials_sent = true
|
166
|
+
perform(&block)
|
134
167
|
end
|
135
168
|
|
136
169
|
def raw_body
|
@@ -175,19 +208,39 @@ module HTTParty
|
|
175
208
|
@raw_request = http_method.new(request_uri(uri))
|
176
209
|
@raw_request.body = body if body
|
177
210
|
@raw_request.body_stream = options[:body_stream] if options[:body_stream]
|
178
|
-
|
179
|
-
|
180
|
-
|
211
|
+
if options[:headers].respond_to?(:to_hash)
|
212
|
+
headers_hash = options[:headers].to_hash
|
213
|
+
|
214
|
+
@raw_request.initialize_http_header(headers_hash)
|
215
|
+
# If the caller specified a header of 'Accept-Encoding', assume they want to
|
216
|
+
# deal with encoding of content. Disable the internal logic in Net:HTTP
|
217
|
+
# that handles encoding, if the platform supports it.
|
218
|
+
if @raw_request.respond_to?(:decode_content) && (headers_hash.key?('Accept-Encoding') || headers_hash.key?('accept-encoding'))
|
219
|
+
# Using the '[]=' sets decode_content to false
|
220
|
+
@raw_request['accept-encoding'] = @raw_request['accept-encoding']
|
221
|
+
end
|
222
|
+
end
|
223
|
+
if options[:basic_auth] && send_authorization_header?
|
224
|
+
@raw_request.basic_auth(username, password)
|
225
|
+
@credentials_sent = true
|
226
|
+
end
|
227
|
+
setup_digest_auth if digest_auth? && response_unauthorized? && response_has_digest_auth_challenge?
|
181
228
|
end
|
182
229
|
|
183
|
-
def
|
184
|
-
|
185
|
-
|
186
|
-
res = http.request(auth_request)
|
230
|
+
def digest_auth?
|
231
|
+
!!options[:digest_auth]
|
232
|
+
end
|
187
233
|
|
188
|
-
|
189
|
-
|
190
|
-
|
234
|
+
def response_unauthorized?
|
235
|
+
!!last_response && last_response.code == '401'
|
236
|
+
end
|
237
|
+
|
238
|
+
def response_has_digest_auth_challenge?
|
239
|
+
!last_response['www-authenticate'].nil? && last_response['www-authenticate'].length > 0
|
240
|
+
end
|
241
|
+
|
242
|
+
def setup_digest_auth
|
243
|
+
@raw_request.digest_auth(username, password, last_response)
|
191
244
|
end
|
192
245
|
|
193
246
|
def query_string(uri)
|
@@ -223,10 +276,11 @@ module HTTParty
|
|
223
276
|
end
|
224
277
|
|
225
278
|
def encode_with_ruby_encoding(body, charset)
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
279
|
+
if Encoding.name_list.include?(charset)
|
280
|
+
body.force_encoding(charset)
|
281
|
+
else
|
282
|
+
body
|
283
|
+
end
|
230
284
|
end
|
231
285
|
|
232
286
|
def assume_utf16_is_big_endian
|
@@ -298,22 +352,6 @@ module HTTParty
|
|
298
352
|
end
|
299
353
|
end
|
300
354
|
|
301
|
-
# Inspired by Ruby 1.9
|
302
|
-
def handle_deflation
|
303
|
-
return if response_redirects?
|
304
|
-
return if last_response.body.nil?
|
305
|
-
|
306
|
-
case last_response["content-encoding"]
|
307
|
-
when "gzip", "x-gzip"
|
308
|
-
body_io = StringIO.new(last_response.body)
|
309
|
-
last_response.body.replace Zlib::GzipReader.new(body_io).read
|
310
|
-
last_response.delete('content-encoding')
|
311
|
-
when "deflate"
|
312
|
-
last_response.body.replace Zlib::Inflate.inflate(last_response.body)
|
313
|
-
last_response.delete('content-encoding')
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
355
|
def handle_host_redirection
|
318
356
|
check_duplicate_location_header
|
319
357
|
redirect_path = options[:uri_adapter].parse last_response['location']
|
@@ -329,7 +367,7 @@ module HTTParty
|
|
329
367
|
end
|
330
368
|
|
331
369
|
def send_authorization_header?
|
332
|
-
|
370
|
+
!@changed_hosts
|
333
371
|
end
|
334
372
|
|
335
373
|
def response_redirects?
|
@@ -350,6 +388,7 @@ module HTTParty
|
|
350
388
|
cookies_hash = HTTParty::CookieHash.new
|
351
389
|
cookies_hash.add_cookies(options[:headers].to_hash['Cookie']) if options[:headers] && options[:headers].to_hash['Cookie']
|
352
390
|
response.get_fields('Set-Cookie').each { |cookie| cookies_hash.add_cookies(cookie) }
|
391
|
+
|
353
392
|
options[:headers] ||= {}
|
354
393
|
options[:headers]['Cookie'] = cookies_hash.to_cookie_string
|
355
394
|
end
|
@@ -381,6 +420,7 @@ module HTTParty
|
|
381
420
|
if path.userinfo
|
382
421
|
username, password = path.userinfo.split(':')
|
383
422
|
options[:basic_auth] = {username: username, password: password}
|
423
|
+
@credentials_sent = true
|
384
424
|
end
|
385
425
|
end
|
386
426
|
end
|