httparty 0.8.0 → 0.9.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.

@@ -0,0 +1,116 @@
1
+ module HTTParty
2
+ # Default connection adapter that returns a new Net::HTTP each time
3
+ #
4
+ # == Custom Connection Factories
5
+ #
6
+ # If you like to implement your own connection adapter, subclassing
7
+ # HTTPParty::ConnectionAdapter will make it easier. Just override
8
+ # the #connection method. The uri and options attributes will have
9
+ # all the info you need to construct your http connection. Whatever
10
+ # you return from your connection method needs to adhere to the
11
+ # Net::HTTP interface as this is what HTTParty expects.
12
+ #
13
+ # @example log the uri and options
14
+ # class LoggingConnectionAdapter < HTTParty::ConnectionAdapter
15
+ # def connection
16
+ # puts uri
17
+ # puts options
18
+ # Net::HTTP.new(uri)
19
+ # end
20
+ # end
21
+ #
22
+ # @example count number of http calls
23
+ # class CountingConnectionAdapter < HTTParty::ConnectionAdapter
24
+ # @@count = 0
25
+ #
26
+ # self.count
27
+ # @@count
28
+ # end
29
+ #
30
+ # def connection
31
+ # self.count += 1
32
+ # super
33
+ # end
34
+ # end
35
+ #
36
+ # === Configuration
37
+ # There is lots of configuration data available for your connection adapter
38
+ # in the #options attribute. It is up to you to interpret them within your
39
+ # connection adapter. Take a look at the implementation of
40
+ # HTTParty::ConnectionAdapter#connection for examples of how they are used.
41
+ # Something are probably interesting are as follows:
42
+ # * :+timeout+: timeout in seconds
43
+ # * :+debug_output+: see HTTParty::ClassMethods.debug_output.
44
+ # * :+pem+: contains pem data. see HTTParty::ClassMethods.pem.
45
+ # * :+ssl_ca_file+: see HTTParty::ClassMethods.ssl_ca_file.
46
+ # * :+ssl_ca_path+: see HTTParty::ClassMethods.ssl_ca_path.
47
+ # * :+connection_adapter_options+: contains the hash your passed to HTTParty.connection_adapter when you configured your connection adapter
48
+ class ConnectionAdapter
49
+
50
+ def self.call(uri, options)
51
+ new(uri, options).connection
52
+ end
53
+
54
+ attr_reader :uri, :options
55
+
56
+ def initialize(uri, options={})
57
+ raise ArgumentError, "uri must be a URI, not a #{uri.class}" unless uri.kind_of? URI
58
+
59
+ @uri = uri
60
+ @options = options
61
+ end
62
+
63
+ def connection
64
+ http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport], options[:http_proxyuser], options[:http_proxypass])
65
+
66
+ http.use_ssl = ssl_implied?(uri)
67
+
68
+ attach_ssl_certificates(http, options)
69
+
70
+ if options[:timeout] && (options[:timeout].is_a?(Integer) || options[:timeout].is_a?(Float))
71
+ http.open_timeout = options[:timeout]
72
+ http.read_timeout = options[:timeout]
73
+ end
74
+
75
+ if options[:debug_output]
76
+ http.set_debug_output(options[:debug_output])
77
+ end
78
+
79
+ return http
80
+ end
81
+
82
+ private
83
+ def ssl_implied?(uri)
84
+ uri.port == 443 || uri.instance_of?(URI::HTTPS)
85
+ end
86
+
87
+ def attach_ssl_certificates(http, options)
88
+ if http.use_ssl?
89
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
90
+
91
+ # Client certificate authentication
92
+ if options[:pem]
93
+ http.cert = OpenSSL::X509::Certificate.new(options[:pem])
94
+ http.key = OpenSSL::PKey::RSA.new(options[:pem], options[:pem_password])
95
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
96
+ end
97
+
98
+ # SSL certificate authority file and/or directory
99
+ if options[:ssl_ca_file]
100
+ http.ca_file = options[:ssl_ca_file]
101
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
102
+ end
103
+
104
+ if options[:ssl_ca_path]
105
+ http.ca_path = options[:ssl_ca_path]
106
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
107
+ end
108
+
109
+ # This is only Ruby 1.9+
110
+ if options[:ssl_version] && http.respond_to?(:ssl_version=)
111
+ http.ssl_version = options[:ssl_version]
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -1,7 +1,7 @@
1
1
  class HTTParty::CookieHash < Hash #:nodoc:
2
-
2
+
3
3
  CLIENT_COOKIES = %w{path expires domain path secure HTTPOnly}
4
-
4
+
5
5
  def add_cookies(value)
6
6
  case value
7
7
  when Hash
@@ -6,4 +6,27 @@ module HTTParty
6
6
  instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval/ }
7
7
  end
8
8
  end
9
+
10
+ unless defined?(Net::HTTP::Patch)
11
+ class Net::HTTP
12
+ def patch(path, data, initheader = nil, dest = nil, &block) #:nodoc:
13
+ res = nil
14
+ request(Patch.new(path, initheader), data) {|r|
15
+ r.read_body dest, &block
16
+ res = r
17
+ }
18
+ unless @newimpl
19
+ res.value
20
+ return res, res.body
21
+ end
22
+ res
23
+ end
24
+
25
+ class Patch < Net::HTTPRequest
26
+ METHOD = 'PATCH'
27
+ REQUEST_HAS_BODY = true
28
+ RESPONSE_HAS_BODY = true
29
+ end
30
+ end
31
+ end
9
32
  end
@@ -48,4 +48,4 @@ module HTTParty
48
48
  param
49
49
  end
50
50
  end
51
- end
51
+ end
@@ -4,6 +4,16 @@ module HTTParty
4
4
  base.extend(ClassMethods)
5
5
  end
6
6
 
7
+ # borrowed from Rails 3.2 ActiveSupport
8
+ def self.hash_deep_dup(h)
9
+ duplicate = h.dup
10
+ duplicate.each_pair do |k,v|
11
+ tv = duplicate[k]
12
+ duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? hash_deep_dup(tv) : v
13
+ end
14
+ duplicate
15
+ end
16
+
7
17
  module ClassMethods #:nodoc:
8
18
  def mattr_inheritable(*args)
9
19
  @mattr_inheritable_attrs ||= [:mattr_inheritable_attrs]
@@ -22,7 +32,7 @@ module HTTParty
22
32
  if instance_variable_get(ivar).respond_to?(:merge)
23
33
  method = <<-EOM
24
34
  def self.#{inheritable_attribute}
25
- #{ivar} = superclass.#{inheritable_attribute}.merge #{ivar}
35
+ #{ivar} = superclass.#{inheritable_attribute}.merge ModuleInheritableAttributes.hash_deep_dup(#{ivar})
26
36
  end
27
37
  EOM
28
38
  subclass.class_eval method
@@ -20,15 +20,24 @@ module Net
20
20
 
21
21
  def authorization_header
22
22
  @cnonce = md5(random)
23
- header = [%Q(Digest username="#{@username}"),
23
+ header = [
24
+ %Q(Digest username="#{@username}"),
24
25
  %Q(realm="#{@response['realm']}"),
25
26
  %Q(nonce="#{@response['nonce']}"),
26
27
  %Q(uri="#{@path}"),
27
- %Q(response="#{request_digest}")]
28
- [%Q(cnonce="#{@cnonce}"),
29
- %Q(opaque="#{@response['opaque']}"),
30
- %Q(qop="#{@response['qop']}"),
31
- %Q(nc="0")].each { |field| header << field } if qop_present?
28
+ %Q(response="#{request_digest}"),
29
+ ]
30
+
31
+ if qop_present?
32
+ fields = [
33
+ %Q(cnonce="#{@cnonce}"),
34
+ %Q(qop="#{@response['qop']}"),
35
+ %Q(nc="00000001")
36
+ ]
37
+ fields.each { |field| header << field }
38
+ end
39
+
40
+ header << %Q(opaque="#{@response['opaque']}") if opaque_present?
32
41
  header
33
42
  end
34
43
 
@@ -41,6 +50,10 @@ module Net
41
50
  params
42
51
  end
43
52
 
53
+ def opaque_present?
54
+ @response.has_key?('opaque') and not @response['opaque'].empty?
55
+ end
56
+
44
57
  def qop_present?
45
58
  @response.has_key?('qop') and not @response['qop'].empty?
46
59
  end
@@ -51,7 +64,7 @@ module Net
51
64
 
52
65
  def request_digest
53
66
  a = [md5(a1), @response['nonce'], md5(a2)]
54
- a.insert(2, "0", @cnonce, @response['qop']) if qop_present?
67
+ a.insert(2, "00000001", @cnonce, @response['qop']) if qop_present?
55
68
  md5(a.join(":"))
56
69
  end
57
70
 
@@ -93,12 +93,11 @@ module HTTParty
93
93
  @body = body
94
94
  @format = format
95
95
  end
96
- private_class_method :new
97
96
 
98
97
  # @return [Object] the parsed body
99
- # @return [nil] when the response body is nil or an empty string
98
+ # @return [nil] when the response body is nil, an empty string, spaces only or "null"
100
99
  def parse
101
- return nil if body.nil? || body.empty?
100
+ return nil if body.nil? || body.strip.empty? || body == "null"
102
101
  if supports_format?
103
102
  parse_supported_format
104
103
  else
@@ -113,7 +112,12 @@ module HTTParty
113
112
  end
114
113
 
115
114
  def json
116
- MultiJson.decode(body)
115
+ # https://github.com/sferik/rails/commit/5e62670131dfa1718eaf21ff8dd3371395a5f1cc
116
+ if MultiJson.respond_to?(:adapter)
117
+ MultiJson.load(body)
118
+ else
119
+ MultiJson.decode(body)
120
+ end
117
121
  end
118
122
 
119
123
  def yaml
@@ -134,8 +138,8 @@ module HTTParty
134
138
 
135
139
  def parse_supported_format
136
140
  send(format)
137
- rescue NoMethodError
138
- raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format."
141
+ rescue NoMethodError => e
142
+ raise NotImplementedError, "#{self.class.name} has not implemented a parsing method for the #{format.inspect} format.", e.backtrace
139
143
  end
140
144
  end
141
145
  end
@@ -3,6 +3,7 @@ module HTTParty
3
3
  SupportedHTTPMethods = [
4
4
  Net::HTTP::Get,
5
5
  Net::HTTP::Post,
6
+ Net::HTTP::Patch,
6
7
  Net::HTTP::Put,
7
8
  Net::HTTP::Delete,
8
9
  Net::HTTP::Head,
@@ -32,7 +33,8 @@ module HTTParty
32
33
  :limit => o.delete(:no_follow) ? 1 : 5,
33
34
  :default_params => {},
34
35
  :follow_redirects => true,
35
- :parser => Parser
36
+ :parser => Parser,
37
+ :connection_adapter => ConnectionAdapter
36
38
  }.merge(o)
37
39
  end
38
40
 
@@ -67,60 +69,36 @@ module HTTParty
67
69
  options[:parser]
68
70
  end
69
71
 
70
- def perform
71
- validate
72
- setup_raw_request
73
- self.last_response = http.request(@raw_request)
74
- handle_deflation
75
- handle_response
72
+ def connection_adapter
73
+ options[:connection_adapter]
76
74
  end
77
75
 
78
- private
79
-
80
- def attach_ssl_certificates(http)
81
- if http.use_ssl?
82
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
76
+ def perform(&block)
77
+ validate
78
+ setup_raw_request
79
+ chunked_body = nil
83
80
 
84
- # Client certificate authentication
85
- if options[:pem]
86
- http.cert = OpenSSL::X509::Certificate.new(options[:pem])
87
- http.key = OpenSSL::PKey::RSA.new(options[:pem], options[:pem_password])
88
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
89
- end
81
+ self.last_response = http.request(@raw_request) do |http_response|
82
+ if block
83
+ chunks = []
90
84
 
91
- # SSL certificate authority file and/or directory
92
- if options[:ssl_ca_file]
93
- http.ca_file = options[:ssl_ca_file]
94
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
95
- end
85
+ http_response.read_body do |fragment|
86
+ chunks << fragment
87
+ block.call(fragment)
88
+ end
96
89
 
97
- if options[:ssl_ca_path]
98
- http.ca_path = options[:ssl_ca_path]
99
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
90
+ chunked_body = chunks.join
100
91
  end
101
92
  end
102
- end
103
-
104
- def http
105
- http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
106
- http.use_ssl = ssl_implied?
107
-
108
- if options[:timeout] && (options[:timeout].is_a?(Integer) || options[:timeout].is_a?(Float))
109
- http.open_timeout = options[:timeout]
110
- http.read_timeout = options[:timeout]
111
- end
112
-
113
- attach_ssl_certificates(http)
114
-
115
- if options[:debug_output]
116
- http.set_debug_output(options[:debug_output])
117
- end
118
93
 
119
- http
94
+ handle_deflation
95
+ handle_response(chunked_body)
120
96
  end
121
97
 
122
- def ssl_implied?
123
- uri.port == 443 || uri.instance_of?(URI::HTTPS)
98
+ private
99
+
100
+ def http
101
+ connection_adapter.call(uri, options)
124
102
  end
125
103
 
126
104
  def body
@@ -160,7 +138,10 @@ module HTTParty
160
138
  end
161
139
 
162
140
  def setup_digest_auth
163
- res = http.head(uri.request_uri, options[:headers])
141
+ auth_request = http_method.new(uri.request_uri)
142
+ auth_request.initialize_http_header(options[:headers])
143
+ res = http.request(auth_request)
144
+
164
145
  if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
165
146
  @raw_request.digest_auth(username, password, res)
166
147
  end
@@ -180,8 +161,7 @@ module HTTParty
180
161
  query_string_parts.size > 0 ? query_string_parts.join('&') : nil
181
162
  end
182
163
 
183
- # Raises exception Net::XXX (http error code) if an http error occured
184
- def handle_response
164
+ def handle_response(body)
185
165
  if response_redirects?
186
166
  options[:limit] -= 1
187
167
  self.path = last_response['location']
@@ -190,18 +170,21 @@ module HTTParty
190
170
  capture_cookies(last_response)
191
171
  perform
192
172
  else
193
- Response.new(self, last_response, parse_response(last_response.body))
173
+ body = body || last_response.body
174
+ Response.new(self, last_response, lambda { parse_response(body) }, :body => body)
194
175
  end
195
176
  end
196
177
 
197
178
  # Inspired by Ruby 1.9
198
179
  def handle_deflation
199
180
  case last_response["content-encoding"]
200
- when "gzip"
181
+ when "gzip", "x-gzip"
201
182
  body_io = StringIO.new(last_response.body)
202
183
  last_response.body.replace Zlib::GzipReader.new(body_io).read
184
+ last_response.delete('content-encoding')
203
185
  when "deflate"
204
186
  last_response.body.replace Zlib::Inflate.inflate(last_response.body)
187
+ last_response.delete('content-encoding')
205
188
  end
206
189
  end
207
190
 
@@ -241,7 +224,7 @@ module HTTParty
241
224
 
242
225
  def validate
243
226
  raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
244
- raise ArgumentError, 'only get, post, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
227
+ raise ArgumentError, 'only get, post, patch, put, delete, head, and options methods are supported' unless SupportedHTTPMethods.include?(http_method)
245
228
  raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
246
229
  raise ArgumentError, 'only one authentication method, :basic_auth or :digest_auth may be used at a time' if options[:basic_auth] && options[:digest_auth]
247
230
  raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
@@ -1,46 +1,21 @@
1
1
  module HTTParty
2
2
  class Response < HTTParty::BasicObject #:nodoc:
3
- class Headers
4
- include ::Net::HTTPHeader
5
-
6
- def initialize(header)
7
- @header = header
8
- end
9
-
10
- def ==(other)
11
- @header == other
12
- end
13
-
14
- def inspect
15
- @header.inspect
16
- end
17
-
18
- def method_missing(name, *args, &block)
19
- if @header.respond_to?(name)
20
- @header.send(name, *args, &block)
21
- else
22
- super
23
- end
24
- end
25
-
26
- def respond_to?(method)
27
- super || @header.respond_to?(method)
28
- end
29
- end
30
-
31
-
32
3
  def self.underscore(string)
33
4
  string.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
34
5
  end
35
6
 
36
- attr_reader :request, :response, :parsed_response, :body, :headers
7
+ attr_reader :request, :response, :body, :headers
37
8
 
38
- def initialize(request, response, parsed_response)
39
- @request = request
40
- @response = response
41
- @body = response.body
42
- @parsed_response = parsed_response
43
- @headers = Headers.new(response.to_hash)
9
+ def initialize(request, response, parsed_block, options={})
10
+ @request = request
11
+ @response = response
12
+ @body = response.body || options[:body]
13
+ @parsed_block = parsed_block
14
+ @headers = Headers.new(response.to_hash)
15
+ end
16
+
17
+ def parsed_response
18
+ @parsed_response ||= @parsed_block.call
44
19
  end
45
20
 
46
21
  def class
@@ -53,7 +28,7 @@ module HTTParty
53
28
 
54
29
  def inspect
55
30
  inspect_id = "%x" % (object_id * 2)
56
- %(#<#{self.class}:0x#{inspect_id} @parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>)
31
+ %(#<#{self.class}:0x#{inspect_id} parsed_response=#{parsed_response.inspect}, @response=#{response.inspect}, @headers=#{headers.inspect}>)
57
32
  end
58
33
 
59
34
  CODES_TO_OBJ = ::Net::HTTPResponse::CODE_CLASS_TO_OBJ.merge ::Net::HTTPResponse::CODE_TO_OBJ
@@ -64,10 +39,10 @@ module HTTParty
64
39
  klass === response
65
40
  end
66
41
  end
67
-
42
+
68
43
  def respond_to?(name)
69
- return true if [:request,:response,:parsed_response,:body,:headers].include?(name)
70
- parsed_response.respond_to?(name) or response.respond_to?(name)
44
+ return true if [:request, :response, :parsed_response, :body, :headers].include?(name)
45
+ parsed_response.respond_to?(name) || response.respond_to?(name)
71
46
  end
72
47
 
73
48
  protected
@@ -83,3 +58,5 @@ module HTTParty
83
58
  end
84
59
  end
85
60
  end
61
+
62
+ require 'httparty/response/headers'