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.

@@ -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'
@@ -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.
@@ -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
@@ -29,4 +29,4 @@ module AAWS
29
29
  end
30
30
 
31
31
  aaws = AAWS::Book.new(config[:access_key])
32
- pp aaws.search(query: {title: 'Ruby On Rails'})
32
+ pp aaws.search(query: { title: 'Ruby On Rails' })
@@ -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 })
@@ -21,7 +21,7 @@ class OnlyParseAtom
21
21
 
22
22
  # Only support Atom
23
23
  class Parser::OnlyAtom < HTTParty::Parser
24
- SupportedFormats = {"application/atom+xml" => :atom}
24
+ SupportedFormats = { "application/atom+xml" => :atom }
25
25
 
26
26
  protected
27
27
 
@@ -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'] }
@@ -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 wich method to call on the logger too. It is info by default."
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
@@ -7,7 +7,7 @@ class StackExchange
7
7
  base_uri 'api.stackexchange.com'
8
8
 
9
9
  def initialize(service, page)
10
- @options = { query: {site: service, page: page} }
10
+ @options = { query: { site: service, page: page } }
11
11
  end
12
12
 
13
13
  def questions
@@ -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)
@@ -24,7 +24,7 @@ class TripIt
24
24
  end
25
25
 
26
26
  def account_settings
27
- self.class.get('/account/edit', headers: {'Cookie' => @cookie.to_cookie_string})
27
+ self.class.get('/account/edit', headers: { 'Cookie' => @cookie.to_cookie_string })
28
28
  end
29
29
 
30
30
  def logged_in?
@@ -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
@@ -7,4 +7,4 @@ class Rep
7
7
  end
8
8
 
9
9
  pp Rep.get('http://whoismyrepresentative.com/getall_mems.php?zip=46544')
10
- pp Rep.get('http://whoismyrepresentative.com/getall_mems.php', query: {zip: 46544})
10
+ pp Rep.get('http://whoismyrepresentative.com/getall_mems.php', query: { zip: 46544 })
@@ -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 = '>= 1.9.3'
16
+ s.required_ruby_version = '>= 2.0.0'
17
17
 
18
18
  s.add_dependency 'multi_xml', ">= 0.5.2"
19
19
 
@@ -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
- # [:+body_steam:] Allow streaming to a REST server to specify a body_stream.
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
- # Specically, don't use bracket notation when sending an array
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
- 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)
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
- # Some things that are probably interesting are as follows:
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
- # * :+pem+: contains pem data. see HTTParty::ClassMethods.pem.
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/
@@ -1,3 +1,5 @@
1
+ require 'erb'
2
+
1
3
  module HTTParty
2
4
  module HashConversions
3
5
  # @return <String> This hash as a query string
@@ -12,13 +12,13 @@ module Net
12
12
  response
13
13
  )
14
14
 
15
- @header['Authorization'] = authenticator.authorization_header
16
- @header['cookie'] = append_cookies(authenticator) if response['Set-Cookie']
17
- end
15
+ authenticator.authorization_header.each do |v|
16
+ add_field('Authorization', v)
17
+ end
18
18
 
19
- def append_cookies(authenticator)
20
- cookies = @header['cookie'] ? @header['cookie'] : []
21
- cookies.concat(authenticator.cookie_header)
19
+ authenticator.cookie_header.each do |v|
20
+ add_field('Cookie', v)
21
+ end
22
22
  end
23
23
 
24
24
  class DigestAuthenticator
@@ -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
@@ -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
- new_uri = path.relative? ? options[:uri_adapter].parse("#{base_uri}#{path}") : path.clone
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
- handle_deflation unless http_method == Net::HTTP::Head
154
+
155
+
132
156
  handle_host_redirection if response_redirects?
133
- handle_response(chunked_body, &block)
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
- @raw_request.initialize_http_header(options[:headers].to_hash) if options[:headers].respond_to?(:to_hash)
179
- @raw_request.basic_auth(username, password) if options[:basic_auth] && send_authorization_header?
180
- setup_digest_auth if options[:digest_auth]
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 setup_digest_auth
184
- auth_request = http_method.new(uri.request_uri)
185
- auth_request.initialize_http_header(options[:headers].to_hash) if options[:headers].respond_to?(:to_hash)
186
- res = http.request(auth_request)
230
+ def digest_auth?
231
+ !!options[:digest_auth]
232
+ end
187
233
 
188
- if !res['www-authenticate'].nil? && res['www-authenticate'].length > 0
189
- @raw_request.digest_auth(username, password, res)
190
- end
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
- encoding = Encoding.find(charset)
227
- body.force_encoding(encoding)
228
- rescue
229
- body
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
- !defined?(@changed_hosts)
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