patron-new 0.4.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/patron.rb ADDED
@@ -0,0 +1,38 @@
1
+ ## -------------------------------------------------------------------
2
+ ##
3
+ ## Patron HTTP Client: Bootstrap script
4
+ ## Copyright (c) 2008 The Hive http://www.thehive.com/
5
+ ##
6
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ ## of this software and associated documentation files (the "Software"), to deal
8
+ ## in the Software without restriction, including without limitation the rights
9
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ ## copies of the Software, and to permit persons to whom the Software is
11
+ ## furnished to do so, subject to the following conditions:
12
+ ##
13
+ ## The above copyright notice and this permission notice shall be included in
14
+ ## all copies or substantial portions of the Software.
15
+ ##
16
+ ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ ## THE SOFTWARE.
23
+ ##
24
+ ## -------------------------------------------------------------------
25
+ require 'pathname'
26
+
27
+ cwd = Pathname(__FILE__).dirname
28
+ $:.unshift(cwd.to_s) unless $:.include?(cwd.to_s) || $:.include?(cwd.expand_path.to_s)
29
+
30
+ require 'patron/session'
31
+ require 'patron/version'
32
+
33
+ module Patron #:nodoc:
34
+ # Returns the version number of the Patron library as a string
35
+ def self.version
36
+ VERSION
37
+ end
38
+ end
@@ -0,0 +1,55 @@
1
+ ## -------------------------------------------------------------------
2
+ ##
3
+ ## Patron HTTP Client: Error definitions
4
+ ## Copyright (c) 2008 The Hive http://www.thehive.com/
5
+ ##
6
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ ## of this software and associated documentation files (the "Software"), to deal
8
+ ## in the Software without restriction, including without limitation the rights
9
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ ## copies of the Software, and to permit persons to whom the Software is
11
+ ## furnished to do so, subject to the following conditions:
12
+ ##
13
+ ## The above copyright notice and this permission notice shall be included in
14
+ ## all copies or substantial portions of the Software.
15
+ ##
16
+ ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ ## THE SOFTWARE.
23
+ ##
24
+ ## -------------------------------------------------------------------
25
+
26
+ module Patron
27
+
28
+ # Base class for Patron exceptions.
29
+ class Error < StandardError; end
30
+
31
+ # The URL you passed to Patron used a protocol that it does not support.
32
+ # This most likely the result of a misspelled protocol string.
33
+ class UnsupportedProtocol < Error; end
34
+
35
+ # The URL was not properly formatted.
36
+ class URLFormatError < Error; end
37
+
38
+ # Could not resolve the remote host name.
39
+ class HostResolutionError < Error; end
40
+
41
+ # Failed to connect to the remote host.
42
+ class ConnectionFailed < Error; end
43
+
44
+ # A file transfer was shorter or larger than expected.
45
+ # This happens when the server first reports an expected transfer size,
46
+ # and then delivers data that doesn't match the previously given size.
47
+ class PartialFileError < Error; end
48
+
49
+ # Operation timeout. The specified time-out period was reached.
50
+ class TimeoutError < Error; end
51
+
52
+ # Too many redirects. When following redirects, Patron hit the maximum amount.
53
+ class TooManyRedirects < Error; end
54
+
55
+ end
@@ -0,0 +1,10 @@
1
+ module Patron
2
+ module ProxyType
3
+ HTTP
4
+ HTTP_1_0
5
+ SOCKS4
6
+ SOCKS5
7
+ SOCKS4A
8
+ SOCKS5_HOSTNAME
9
+ end
10
+ end
@@ -0,0 +1,168 @@
1
+ ## -------------------------------------------------------------------
2
+ ##
3
+ ## Patron HTTP Client: Request class
4
+ ## Copyright (c) 2008 The Hive http://www.thehive.com/
5
+ ##
6
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ ## of this software and associated documentation files (the "Software"), to deal
8
+ ## in the Software without restriction, including without limitation the rights
9
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ ## copies of the Software, and to permit persons to whom the Software is
11
+ ## furnished to do so, subject to the following conditions:
12
+ ##
13
+ ## The above copyright notice and this permission notice shall be included in
14
+ ## all copies or substantial portions of the Software.
15
+ ##
16
+ ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ ## THE SOFTWARE.
23
+ ##
24
+ ## -------------------------------------------------------------------
25
+
26
+ require 'patron/util'
27
+
28
+ module Patron
29
+
30
+ # Represents the information necessary for an HTTP request.
31
+ # This is basically a data object with validation. Not all fields will be
32
+ # used in every request.
33
+ class Request
34
+
35
+ VALID_ACTIONS = %w[GET PUT POST DELETE HEAD COPY]
36
+
37
+ def initialize
38
+ @action = 'GET'
39
+ @headers = {}
40
+ @timeout = 0
41
+ @connect_timeout = 0
42
+ @max_redirects = -1
43
+ end
44
+
45
+ READER_VARS = [
46
+ :url, :username, :password, :file_name, :proxy, :proxy_type, :insecure,
47
+ :ignore_content_length, :multipart, :action, :timeout, :connect_timeout,
48
+ :max_redirects, :headers, :auth_type, :upload_data, :buffer_size, :cacert
49
+ ]
50
+
51
+ WRITER_VARS = [
52
+ :url, :username, :password, :file_name, :proxy, :proxy_type, :insecure,
53
+ :ignore_content_length, :multipart, :cacert
54
+ ]
55
+
56
+ attr_reader *READER_VARS
57
+ attr_writer *WRITER_VARS
58
+
59
+ # Set the type of authentication to use for this request.
60
+ #
61
+ # @param [String, Symbol] type - The type of authentication to use for this request, can be one of
62
+ # :basic, :digest, or :any
63
+ #
64
+ # @example
65
+ # sess.username = "foo"
66
+ # sess.password = "sekrit"
67
+ # sess.auth_type = :digest
68
+ def auth_type=(type=:basic)
69
+ @auth_type = case type
70
+ when :basic, "basic"
71
+ Request::AuthBasic
72
+ when :digest, "digest"
73
+ Request::AuthDigest
74
+ when :any, "any"
75
+ Request::AuthAny
76
+ else
77
+ raise "#{type.inspect} is an unknown authentication type"
78
+ end
79
+ end
80
+
81
+ def upload_data=(data)
82
+ @upload_data = case data
83
+ when Hash
84
+ self.multipart ? data : Util.build_query_string_from_hash(data, action == 'POST')
85
+ else
86
+ data
87
+ end
88
+ end
89
+
90
+ def action=(new_action)
91
+ action = new_action.to_s.upcase
92
+
93
+ if !VALID_ACTIONS.include?(action)
94
+ raise ArgumentError, "Action must be one of #{VALID_ACTIONS.join(', ')}"
95
+ end
96
+
97
+ @action = action
98
+ end
99
+
100
+ def timeout=(new_timeout)
101
+ if new_timeout && new_timeout.to_i < 1
102
+ raise ArgumentError, "Timeout must be a positive integer greater than 0"
103
+ end
104
+
105
+ @timeout = new_timeout.to_i
106
+ end
107
+
108
+ def connect_timeout=(new_timeout)
109
+ if new_timeout && new_timeout.to_i < 1
110
+ raise ArgumentError, "Timeout must be a positive integer greater than 0"
111
+ end
112
+
113
+ @connect_timeout = new_timeout.to_i
114
+ end
115
+
116
+ def max_redirects=(new_max_redirects)
117
+ if new_max_redirects.to_i < -1
118
+ raise ArgumentError, "Max redirects must be a positive integer, 0 or -1"
119
+ end
120
+
121
+ @max_redirects = new_max_redirects.to_i
122
+ end
123
+
124
+ def headers=(new_headers)
125
+ if !new_headers.kind_of?(Hash)
126
+ raise ArgumentError, "Headers must be a hash"
127
+ end
128
+
129
+ @headers = new_headers
130
+ end
131
+
132
+ def buffer_size=(buffer_size)
133
+ if buffer_size != nil && buffer_size.to_i < 1
134
+ raise ArgumentError, "Buffer size must be a positive integer greater than 0 or nil"
135
+ end
136
+
137
+ @buffer_size = buffer_size != nil ? buffer_size.to_i : nil
138
+ end
139
+
140
+ def credentials
141
+ return nil if username.nil? || password.nil?
142
+ "#{username}:#{password}"
143
+ end
144
+
145
+ def eql?(request)
146
+ return false unless Request === request
147
+
148
+ READER_VARS.inject(true) do |memo, name|
149
+ memo && (self.send(name) == request.send(name))
150
+ end
151
+ end
152
+
153
+ alias_method :==, :eql?
154
+
155
+ def marshal_dump
156
+ [ @url, @username, @password, @file_name, @proxy, @proxy_type, @insecure,
157
+ @ignore_content_length, @multipart, @action, @timeout, @connect_timeout,
158
+ @max_redirects, @headers, @auth_type, @upload_data, @buffer_size, @cacert ]
159
+ end
160
+
161
+ def marshal_load(data)
162
+ @url, @username, @password, @file_name, @proxy, @proxy_type, @insecure,
163
+ @ignore_content_length, @multipart, @action, @timeout, @connect_timeout,
164
+ @max_redirects, @headers, @auth_type, @upload_data, @buffer_size, @cacert = data
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,107 @@
1
+ ## -------------------------------------------------------------------
2
+ ##
3
+ ## Patron HTTP Client: Response class
4
+ ## Copyright (c) 2008 The Hive http://www.thehive.com/
5
+ ##
6
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ ## of this software and associated documentation files (the "Software"), to deal
8
+ ## in the Software without restriction, including without limitation the rights
9
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ ## copies of the Software, and to permit persons to whom the Software is
11
+ ## furnished to do so, subject to the following conditions:
12
+ ##
13
+ ## The above copyright notice and this permission notice shall be included in
14
+ ## all copies or substantial portions of the Software.
15
+ ##
16
+ ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ ## THE SOFTWARE.
23
+ ##
24
+ ## -------------------------------------------------------------------
25
+
26
+ module Patron
27
+
28
+ # Represents the response from the HTTP server.
29
+ class Response
30
+
31
+ def initialize(url, status, redirect_count, header_data, body, default_charset = nil)
32
+ # Don't let a response clear out the default charset, which would cause encoding to fail
33
+ default_charset = "ASCII-8BIT" unless default_charset
34
+ @url = url
35
+ @status = status
36
+ @redirect_count = redirect_count
37
+ @body = body
38
+
39
+ @charset = determine_charset(header_data, body) || default_charset
40
+
41
+ [url, header_data].each do |attr|
42
+ convert_to_default_encoding!(attr)
43
+ end
44
+
45
+ parse_headers(header_data)
46
+ if @headers["Content-Type"] && @headers["Content-Type"][0, 5] == "text/"
47
+ convert_to_default_encoding!(@body)
48
+ end
49
+ end
50
+
51
+ attr_reader :url, :status, :status_line, :redirect_count, :body, :headers, :charset
52
+
53
+ def inspect
54
+ # Avoid spamming the console with the header and body data
55
+ "#<Patron::Response @status_line='#{@status_line}'>"
56
+ end
57
+
58
+ def marshal_dump
59
+ [@url, @status, @status_line, @redirect_count, @body, @headers, @charset]
60
+ end
61
+
62
+ def marshal_load(data)
63
+ @url, @status, @status_line, @redirect_count, @body, @headers, @charset = data
64
+ end
65
+
66
+ private
67
+
68
+ def determine_charset(header_data, body)
69
+ header_data.match(charset_regex) || (body && body.match(charset_regex))
70
+
71
+ $1
72
+ end
73
+
74
+ def charset_regex
75
+ /(?:charset|encoding)="?([a-z0-9-]+)"?/i
76
+ end
77
+
78
+ def convert_to_default_encoding!(str)
79
+ if str.respond_to?(:encode) && Encoding.default_internal
80
+ str.force_encoding(charset).encode!(Encoding.default_internal)
81
+ end
82
+ end
83
+
84
+ # Called by the C code to parse and set the headers
85
+ def parse_headers(header_data)
86
+ @headers = {}
87
+
88
+ header_data.split(/\r\n/).each do |header|
89
+ if header =~ %r|^HTTP/1.[01]|
90
+ @status_line = header.strip
91
+ else
92
+ parts = header.split(':', 2)
93
+ unless parts.empty?
94
+ parts[1].strip! unless parts[1].nil?
95
+ if @headers.has_key?(parts[0])
96
+ @headers[parts[0]] = [@headers[parts[0]]] unless @headers[parts[0]].kind_of? Array
97
+ @headers[parts[0]] << parts[1]
98
+ else
99
+ @headers[parts[0]] = parts[1]
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,234 @@
1
+ ## -------------------------------------------------------------------
2
+ ##
3
+ ## Patron HTTP Client: Session class
4
+ ## Copyright (c) 2008 The Hive http://www.thehive.com/
5
+ ##
6
+ ## Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ ## of this software and associated documentation files (the "Software"), to deal
8
+ ## in the Software without restriction, including without limitation the rights
9
+ ## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ ## copies of the Software, and to permit persons to whom the Software is
11
+ ## furnished to do so, subject to the following conditions:
12
+ ##
13
+ ## The above copyright notice and this permission notice shall be included in
14
+ ## all copies or substantial portions of the Software.
15
+ ##
16
+ ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ ## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ ## THE SOFTWARE.
23
+ ##
24
+ ## -------------------------------------------------------------------
25
+
26
+ require 'uri'
27
+ require 'patron/error'
28
+ require 'patron/request'
29
+ require 'patron/response'
30
+ require 'patron/session_ext'
31
+ require 'patron/util'
32
+
33
+ module Patron
34
+
35
+ # This class represents multiple request/response transactions with an HTTP
36
+ # server. This is the primary API for Patron.
37
+ class Session
38
+
39
+ # HTTP connection timeout in seconds. Defaults to 1 second.
40
+ attr_accessor :connect_timeout
41
+
42
+ # HTTP transaction timeout in seconds. Defaults to 5 seconds.
43
+ attr_accessor :timeout
44
+
45
+ # Maximum number of times to follow redirects.
46
+ # Set to 0 to disable and -1 to follow all redirects. Defaults to 5.
47
+ attr_accessor :max_redirects
48
+
49
+ # Prepended to the URL in all requests.
50
+ attr_accessor :base_url
51
+
52
+ # Username and password for http authentication
53
+ attr_accessor :username, :password
54
+
55
+ # Proxy URL in cURL format ('hostname:8080')
56
+ attr_accessor :proxy
57
+
58
+ # Proxy type (default is HTTP), see constants under ProxyType for supported types.
59
+ attr_accessor :proxy_type
60
+
61
+ # Standard set of headers that are used in all requests.
62
+ attr_accessor :headers
63
+
64
+ # Set the authentication type for the request.
65
+ # @see Patron::Request#auth_type
66
+ attr_accessor :auth_type
67
+
68
+ # Does this session stricly verify SSL certificates?
69
+ attr_accessor :insecure
70
+
71
+ # What cacert file should this session use to verify SSL certificates?
72
+ attr_accessor :cacert
73
+
74
+ # Does this session ignore Content-Size headers?
75
+ attr_accessor :ignore_content_length
76
+
77
+ # Set the buffer size for this request. This option will
78
+ # only be set if buffer_size is non-nil
79
+ attr_accessor :buffer_size
80
+
81
+ # Default encoding of responses. Used if no charset is provided by the host.
82
+ attr_accessor :default_response_charset
83
+
84
+ private :handle_request, :enable_cookie_session, :set_debug_file
85
+
86
+ # Create a new Session object.
87
+ def initialize
88
+ @headers = {}
89
+ @timeout = 5
90
+ @connect_timeout = 1
91
+ @max_redirects = 5
92
+ @auth_type = :basic
93
+ end
94
+
95
+ # Turn on cookie handling for this session, storing them in memory by
96
+ # default or in +file+ if specified. The +file+ must be readable and
97
+ # writable. Calling multiple times will add more files.
98
+ def handle_cookies(file = nil)
99
+ if file
100
+ path = Pathname(file).expand_path
101
+ unless File.exists?(file) and File.writable?(path.dirname)
102
+ raise ArgumentError, "Can't create file #{path} (permission error)"
103
+ end
104
+ unless File.readable?(file) or File.writable?(path)
105
+ raise ArgumentError, "Can't read or write file #{path} (permission error)"
106
+ end
107
+ end
108
+ enable_cookie_session(path.to_s)
109
+ self
110
+ end
111
+
112
+ # Enable debug output to stderr or to specified +file+.
113
+ def enable_debug(file = nil)
114
+ set_debug_file(file.to_s)
115
+ end
116
+
117
+ ###################################################################
118
+ ### Standard HTTP methods
119
+ ###
120
+
121
+ # Retrieve the contents of the specified +url+ optionally sending the
122
+ # specified headers. If the +base_url+ varaible is set then it is prepended
123
+ # to the +url+ parameter. Any custom headers are merged with the contents
124
+ # of the +headers+ instance variable. The results are returned in a
125
+ # Response object.
126
+ # Notice: this method doesn't accept any +data+ argument: if you need to send data with
127
+ # a get request, please, use the #request method.
128
+ def get(url, headers = {})
129
+ request(:get, url, headers)
130
+ end
131
+
132
+ # Retrieve the contents of the specified +url+ as with #get, but the
133
+ # content at the URL is downloaded directly into the specified file.
134
+ def get_file(url, filename, headers = {})
135
+ request(:get, url, headers, :file => filename)
136
+ end
137
+
138
+ # As #get but sends an HTTP HEAD request.
139
+ def head(url, headers = {})
140
+ request(:head, url, headers)
141
+ end
142
+
143
+ # As #get but sends an HTTP DELETE request.
144
+ # Notice: this method doesn't accept any +data+ argument: if you need to send data with
145
+ # a delete request, please, use the #request method.
146
+ def delete(url, headers = {})
147
+ request(:delete, url, headers)
148
+ end
149
+
150
+ # Uploads the passed +data+ to the specified +url+ using HTTP PUT. +data+
151
+ # must be a string.
152
+ def put(url, data, headers = {})
153
+ request(:put, url, headers, :data => data)
154
+ end
155
+
156
+ # Uploads the contents of a file to the specified +url+ using HTTP PUT.
157
+ def put_file(url, filename, headers = {})
158
+ request(:put, url, headers, :file => filename)
159
+ end
160
+
161
+ # Uploads the passed +data+ to the specified +url+ using HTTP POST. +data+
162
+ # can be a string or a hash.
163
+ def post(url, data, headers = {})
164
+ if data.is_a?(Hash)
165
+ data = data.map {|k,v| urlencode(k.to_s) + '=' + urlencode(v.to_s) }.join('&')
166
+ headers['Content-Type'] = 'application/x-www-form-urlencoded'
167
+ end
168
+ request(:post, url, headers, :data => data)
169
+ end
170
+
171
+ # Uploads the contents of a file to the specified +url+ using HTTP POST.
172
+ def post_file(url, filename, headers = {})
173
+ request(:post, url, headers, :file => filename)
174
+ end
175
+
176
+ # Uploads the contents of a file and data to the specified +url+ using HTTP POST.
177
+ def post_multipart(url, data, filename, headers = {})
178
+ request(:post, url, headers, {:data => data, :file => filename, :multipart => true})
179
+ end
180
+
181
+ ###################################################################
182
+ ### WebDAV methods
183
+ ###
184
+
185
+ # Sends a WebDAV COPY request to the specified +url+.
186
+ def copy(url, dest, headers = {})
187
+ headers['Destination'] = dest
188
+ request(:copy, url, headers)
189
+ end
190
+
191
+ ###################################################################
192
+ ### Basic API methods
193
+ ###
194
+
195
+ # Send an HTTP request to the specified +url+.
196
+ def request(action, url, headers, options = {})
197
+ # If the Expect header isn't set uploads are really slow
198
+ headers['Expect'] ||= ''
199
+
200
+ req = Request.new
201
+ req.action = action
202
+ req.headers = self.headers.merge headers
203
+ req.timeout = options.fetch :timeout, self.timeout
204
+ req.connect_timeout = options.fetch :connect_timeout, self.connect_timeout
205
+ req.max_redirects = options.fetch :max_redirects, self.max_redirects
206
+ req.username = options.fetch :username, self.username
207
+ req.password = options.fetch :password, self.password
208
+ req.proxy = options.fetch :proxy, self.proxy
209
+ req.proxy_type = options.fetch :proxy_type, self.proxy_type
210
+ req.auth_type = options.fetch :auth_type, self.auth_type
211
+ req.insecure = options.fetch :insecure, self.insecure
212
+ req.cacert = options.fetch :cacert, self.cacert
213
+ req.ignore_content_length = options.fetch :ignore_content_length, self.ignore_content_length
214
+ req.buffer_size = options.fetch :buffer_size, self.buffer_size
215
+ req.multipart = options[:multipart]
216
+ req.upload_data = options[:data]
217
+ req.file_name = options[:file]
218
+
219
+ base_url = self.base_url.to_s
220
+ url = url.to_s
221
+ raise ArgumentError, "Empty URL" if base_url.empty? && url.empty?
222
+ uri = URI.join(base_url, url)
223
+ query = uri.query.to_s.split('&')
224
+ query += options[:query].is_a?(Hash) ? Util.build_query_pairs_from_hash(options[:query]) : options[:query].to_s.split('&')
225
+ uri.query = query.join('&')
226
+ uri.query = nil if uri.query.empty?
227
+ url = uri.to_s
228
+ req.url = url
229
+
230
+ handle_request(req)
231
+ end
232
+
233
+ end
234
+ end