excon 0.16.10 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of excon might be problematic. Click here for more details.
- data/Gemfile.lock +1 -1
- data/README.md +13 -0
- data/excon.gemspec +7 -3
- data/lib/excon.rb +4 -1
- data/lib/excon/connection.rb +218 -205
- data/lib/excon/constants.rb +2 -2
- data/lib/excon/errors.rb +7 -0
- data/lib/excon/middlewares/expects.rb +19 -0
- data/lib/excon/middlewares/instrumentor.rb +26 -0
- data/lib/excon/response.rb +37 -25
- data/lib/excon/socket.rb +37 -20
- data/lib/excon/ssl_socket.rb +20 -25
- data/lib/excon/standard_instrumentor.rb +1 -1
- data/tests/authorization_header_tests.rb +1 -1
- data/tests/{instrumentation_tests.rb → middlewares/instrumentation_tests.rb} +1 -1
- data/tests/proxy_tests.rb +46 -46
- data/tests/rackups/request_headers.ru +11 -0
- data/tests/request_headers_tests.rb +21 -0
- data/tests/test_helper.rb +4 -0
- metadata +9 -5
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -5,6 +5,7 @@ Http(s) EXtended CONnections
|
|
5
5
|
|
6
6
|
[![Build Status](https://secure.travis-ci.org/geemus/excon.png)](http://travis-ci.org/geemus/excon)
|
7
7
|
[![Dependency Status](https://gemnasium.com/geemus/excon.png)](https://gemnasium.com/geemus/excon)
|
8
|
+
[![Gem Version](https://fury-badge.herokuapp.com/rb/excon.png)](http://badge.fury.io/rb/excon)
|
8
9
|
|
9
10
|
Getting Started
|
10
11
|
---------------
|
@@ -57,9 +58,21 @@ Both one-off and persistent connections support many other options. Here are a f
|
|
57
58
|
# this request can be repeated safely, so retry on errors up to 3 times
|
58
59
|
connection.request(:idempotent => true)
|
59
60
|
|
61
|
+
# this request can be repeated safely, retry up to 6 times
|
62
|
+
connection.request(:idempotent => true, :retry_limit => 6)
|
63
|
+
|
60
64
|
# opt out of nonblocking operations for performance and/or as a workaround
|
61
65
|
connection.request(:nonblock => false)
|
62
66
|
|
67
|
+
# set longer connect_timeout (default is 60 seconds)
|
68
|
+
connection.request(:connect_timeout => 360)
|
69
|
+
|
70
|
+
# set longer read_timeout (default is 60 seconds)
|
71
|
+
connection.request(:read_timeout => 360)
|
72
|
+
|
73
|
+
# set longer write_timeout (default is 60 seconds)
|
74
|
+
connection.request(:write_timeout => 360)
|
75
|
+
|
63
76
|
These options can be combined to make pretty much any request you might need.
|
64
77
|
|
65
78
|
Excon can also expect one or more HTTP status code in response, raising an exception if the response does not meet the criteria.
|
data/excon.gemspec
CHANGED
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
|
|
13
13
|
## If your rubyforge_project name is different, then edit it and comment out
|
14
14
|
## the sub! line in the Rakefile
|
15
15
|
s.name = 'excon'
|
16
|
-
s.version = '0.
|
17
|
-
s.date = '
|
16
|
+
s.version = '0.17.0'
|
17
|
+
s.date = '2013-02-01'
|
18
18
|
s.rubyforge_project = 'excon'
|
19
19
|
|
20
20
|
## Make sure your summary is short. The description may be as long
|
@@ -96,6 +96,8 @@ Gem::Specification.new do |s|
|
|
96
96
|
lib/excon/connection.rb
|
97
97
|
lib/excon/constants.rb
|
98
98
|
lib/excon/errors.rb
|
99
|
+
lib/excon/middlewares/expects.rb
|
100
|
+
lib/excon/middlewares/instrumentor.rb
|
99
101
|
lib/excon/response.rb
|
100
102
|
lib/excon/socket.rb
|
101
103
|
lib/excon/ssl_socket.rb
|
@@ -106,7 +108,7 @@ Gem::Specification.new do |s|
|
|
106
108
|
tests/data/xs
|
107
109
|
tests/header_tests.rb
|
108
110
|
tests/idempotent_tests.rb
|
109
|
-
tests/instrumentation_tests.rb
|
111
|
+
tests/middlewares/instrumentation_tests.rb
|
110
112
|
tests/proxy_tests.rb
|
111
113
|
tests/query_string_tests.rb
|
112
114
|
tests/rackups/basic.rb
|
@@ -114,11 +116,13 @@ Gem::Specification.new do |s|
|
|
114
116
|
tests/rackups/basic_auth.ru
|
115
117
|
tests/rackups/proxy.ru
|
116
118
|
tests/rackups/query_string.ru
|
119
|
+
tests/rackups/request_headers.ru
|
117
120
|
tests/rackups/request_methods.ru
|
118
121
|
tests/rackups/response_header.ru
|
119
122
|
tests/rackups/ssl.ru
|
120
123
|
tests/rackups/thread_safety.ru
|
121
124
|
tests/rackups/timeout.ru
|
125
|
+
tests/request_headers_tests.rb
|
122
126
|
tests/request_method_tests.rb
|
123
127
|
tests/servers/bad.rb
|
124
128
|
tests/servers/eof.rb
|
data/lib/excon.rb
CHANGED
@@ -19,6 +19,7 @@ module Excon
|
|
19
19
|
:chunk_size => CHUNK_SIZE || DEFAULT_CHUNK_SIZE,
|
20
20
|
:connect_timeout => 60,
|
21
21
|
:headers => {},
|
22
|
+
:idempotent => false,
|
22
23
|
:instrumentor_name => 'excon',
|
23
24
|
:mock => false,
|
24
25
|
:nonblock => DEFAULT_NONBLOCK,
|
@@ -42,6 +43,8 @@ end
|
|
42
43
|
require 'excon/constants'
|
43
44
|
require 'excon/connection'
|
44
45
|
require 'excon/errors'
|
46
|
+
require 'excon/middlewares/expects'
|
47
|
+
require 'excon/middlewares/instrumentor'
|
45
48
|
require 'excon/response'
|
46
49
|
require 'excon/socket'
|
47
50
|
require 'excon/ssl_socket'
|
@@ -108,7 +111,7 @@ module Excon
|
|
108
111
|
request_params.update(
|
109
112
|
:host => uri.host,
|
110
113
|
:path => uri.path,
|
111
|
-
:port => uri.port
|
114
|
+
:port => uri.port,
|
112
115
|
:query => uri.query,
|
113
116
|
:scheme => uri.scheme
|
114
117
|
)
|
data/lib/excon/connection.rb
CHANGED
@@ -1,6 +1,25 @@
|
|
1
1
|
module Excon
|
2
2
|
class Connection
|
3
|
-
|
3
|
+
|
4
|
+
attr_reader :data
|
5
|
+
|
6
|
+
def params
|
7
|
+
$stderr.puts("Excon::Connection#params is deprecated use Excon::Connection#data instead (#{caller.first})")
|
8
|
+
@data
|
9
|
+
end
|
10
|
+
def params=(new_params)
|
11
|
+
$stderr.puts("Excon::Connection#params= is deprecated use Excon::Connection#data= instead (#{caller.first})")
|
12
|
+
@data = new_params
|
13
|
+
end
|
14
|
+
|
15
|
+
def proxy
|
16
|
+
$stderr.puts("Excon::Connection#proxy is deprecated use Excon::Connection#data[:proxy] instead (#{caller.first})")
|
17
|
+
@data[:proxy]
|
18
|
+
end
|
19
|
+
def proxy=(new_proxy)
|
20
|
+
$stderr.puts("Excon::Connection#proxy= is deprecated use Excon::Connection#data[:proxy]= instead (#{caller.first})")
|
21
|
+
@data[:proxy] = new_proxy
|
22
|
+
end
|
4
23
|
|
5
24
|
# Initializes a new Connection instance
|
6
25
|
# @param [String] url The destination URL
|
@@ -18,49 +37,153 @@ module Excon
|
|
18
37
|
# @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon'
|
19
38
|
def initialize(url, params = {})
|
20
39
|
uri = URI.parse(url)
|
21
|
-
@
|
40
|
+
@data = Excon.defaults.merge({
|
22
41
|
:host => uri.host,
|
23
42
|
:host_port => '' << uri.host << ':' << uri.port.to_s,
|
24
43
|
:path => uri.path,
|
25
|
-
:port => uri.port
|
44
|
+
:port => uri.port,
|
26
45
|
:query => uri.query,
|
27
46
|
:scheme => uri.scheme,
|
28
47
|
}).merge!(params)
|
29
48
|
# merge does not deep-dup, so make sure headers is not the original
|
30
|
-
@
|
31
|
-
|
32
|
-
@proxy = nil
|
49
|
+
@data[:headers] = @data[:headers].dup
|
33
50
|
|
34
|
-
if @
|
35
|
-
@proxy = setup_proxy(ENV['https_proxy'] || ENV['HTTPS_PROXY'])
|
51
|
+
if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
|
52
|
+
@data[:proxy] = setup_proxy(ENV['https_proxy'] || ENV['HTTPS_PROXY'])
|
36
53
|
elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
|
37
|
-
@proxy = setup_proxy(ENV['http_proxy'] || ENV['HTTP_PROXY'])
|
38
|
-
elsif @
|
39
|
-
@proxy = setup_proxy(@
|
54
|
+
@data[:proxy] = setup_proxy(ENV['http_proxy'] || ENV['HTTP_PROXY'])
|
55
|
+
elsif @data.has_key?(:proxy)
|
56
|
+
@data[:proxy] = setup_proxy(@data[:proxy])
|
40
57
|
end
|
41
58
|
|
42
|
-
if @proxy
|
43
|
-
@
|
59
|
+
if @data[:proxy]
|
60
|
+
@data[:headers]['Proxy-Connection'] ||= 'Keep-Alive'
|
44
61
|
# https credentials happen in handshake
|
45
|
-
if @
|
46
|
-
auth = ['' << @proxy[:user].to_s << ':' << @proxy[:password].to_s].pack('m').delete(Excon::CR_NL)
|
47
|
-
@
|
62
|
+
if @data[:scheme] == 'http' && (@data[:proxy][:user] || @data[:proxy][:password])
|
63
|
+
auth = ['' << @data[:proxy][:user].to_s << ':' << @data[:proxy][:password].to_s].pack('m').delete(Excon::CR_NL)
|
64
|
+
@data[:headers]['Proxy-Authorization'] = 'Basic ' << auth
|
48
65
|
end
|
49
66
|
end
|
50
67
|
|
51
68
|
if ENV.has_key?('EXCON_DEBUG') || ENV.has_key?('EXCON_STANDARD_INSTRUMENTOR')
|
52
|
-
@
|
69
|
+
@data[:instrumentor] = Excon::StandardInstrumentor
|
53
70
|
end
|
54
71
|
|
55
72
|
# Use Basic Auth if url contains a login
|
56
73
|
if uri.user || uri.password
|
57
|
-
@
|
74
|
+
@data[:headers]['Authorization'] ||= 'Basic ' << ['' << uri.user.to_s << ':' << uri.password.to_s].pack('m').delete(Excon::CR_NL)
|
58
75
|
end
|
59
76
|
|
60
|
-
@socket_key = '' << @
|
77
|
+
@socket_key = '' << @data[:host_port]
|
61
78
|
reset
|
62
79
|
end
|
63
80
|
|
81
|
+
def call(datum)
|
82
|
+
begin
|
83
|
+
response_datum = if datum[:mock]
|
84
|
+
invoke_stub(datum)
|
85
|
+
else
|
86
|
+
socket.data = datum
|
87
|
+
# start with "METHOD /path"
|
88
|
+
request = datum[:method].to_s.upcase << ' '
|
89
|
+
if @data[:proxy]
|
90
|
+
request << datum[:scheme] << '://' << datum[:host_port]
|
91
|
+
end
|
92
|
+
request << datum[:path]
|
93
|
+
|
94
|
+
# add query to path, if there is one
|
95
|
+
case datum[:query]
|
96
|
+
when String
|
97
|
+
request << '?' << datum[:query]
|
98
|
+
when Hash
|
99
|
+
request << '?'
|
100
|
+
datum[:query].each do |key, values|
|
101
|
+
if values.nil?
|
102
|
+
request << key.to_s << '&'
|
103
|
+
else
|
104
|
+
[values].flatten.each do |value|
|
105
|
+
request << key.to_s << '=' << CGI.escape(value.to_s) << '&'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
request.chop! # remove trailing '&'
|
110
|
+
end
|
111
|
+
|
112
|
+
# finish first line with "HTTP/1.1\r\n"
|
113
|
+
request << HTTP_1_1
|
114
|
+
|
115
|
+
if datum.has_key?(:request_block)
|
116
|
+
datum[:headers]['Transfer-Encoding'] = 'chunked'
|
117
|
+
elsif ! (datum[:method].to_s.casecmp('GET') == 0 && datum[:body].nil?)
|
118
|
+
# The HTTP spec isn't clear on it, but specifically, GET requests don't usually send bodies;
|
119
|
+
# if they don't, sending Content-Length:0 can cause issues.
|
120
|
+
datum[:headers]['Content-Length'] = detect_content_length(datum[:body])
|
121
|
+
end
|
122
|
+
|
123
|
+
# add headers to request
|
124
|
+
datum[:headers].each do |key, values|
|
125
|
+
[values].flatten.each do |value|
|
126
|
+
request << key.to_s << ': ' << value.to_s << CR_NL
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# add additional "\r\n" to indicate end of headers
|
131
|
+
request << CR_NL
|
132
|
+
|
133
|
+
# write out the request, sans body
|
134
|
+
socket.write(request)
|
135
|
+
|
136
|
+
# write out the body
|
137
|
+
if datum.has_key?(:request_block)
|
138
|
+
while true
|
139
|
+
chunk = datum[:request_block].call
|
140
|
+
if FORCE_ENC
|
141
|
+
chunk.force_encoding('BINARY')
|
142
|
+
end
|
143
|
+
if chunk.length > 0
|
144
|
+
socket.write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
|
145
|
+
else
|
146
|
+
socket.write('0' << CR_NL << CR_NL)
|
147
|
+
break
|
148
|
+
end
|
149
|
+
end
|
150
|
+
elsif !datum[:body].nil?
|
151
|
+
if datum[:body].is_a?(String)
|
152
|
+
unless datum[:body].empty?
|
153
|
+
socket.write(datum[:body])
|
154
|
+
end
|
155
|
+
else
|
156
|
+
if datum[:body].respond_to?(:binmode)
|
157
|
+
datum[:body].binmode
|
158
|
+
end
|
159
|
+
if datum[:body].respond_to?(:pos=)
|
160
|
+
datum[:body].pos = 0
|
161
|
+
end
|
162
|
+
while chunk = datum[:body].read(datum[:chunk_size])
|
163
|
+
socket.write(chunk)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# read the response
|
169
|
+
response_datum = Excon::Response.parse(socket, datum)
|
170
|
+
|
171
|
+
if response_datum[:headers]['Connection'] == 'close'
|
172
|
+
reset
|
173
|
+
end
|
174
|
+
|
175
|
+
response_datum
|
176
|
+
end
|
177
|
+
rescue Excon::Errors::StubNotFound, Excon::Errors::Timeout => error
|
178
|
+
raise(error)
|
179
|
+
rescue => socket_error
|
180
|
+
reset
|
181
|
+
raise(Excon::Errors::SocketError.new(socket_error))
|
182
|
+
end
|
183
|
+
|
184
|
+
response_datum
|
185
|
+
end
|
186
|
+
|
64
187
|
# Sends the supplied request to the destination host.
|
65
188
|
# @yield [chunk] @see Response#self.parse
|
66
189
|
# @param [Hash<Symbol, >] params One or more optional params, override defaults set in Connection.new
|
@@ -72,53 +195,50 @@ module Excon
|
|
72
195
|
# @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value'
|
73
196
|
# @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
|
74
197
|
def request(params, &block)
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
198
|
+
# @data has defaults, merge in new params to override
|
199
|
+
datum = @data.merge(params)
|
200
|
+
datum[:host_port] = '' << datum[:host] << ':' << datum[:port].to_s
|
201
|
+
datum[:headers] = @data[:headers].merge(datum[:headers] || {})
|
202
|
+
datum[:headers]['Host'] = '' << datum[:host_port]
|
203
|
+
datum[:retries_remaining] ||= datum[:retry_limit]
|
80
204
|
|
81
205
|
# if path is empty or doesn't start with '/', insert one
|
82
|
-
unless
|
83
|
-
|
206
|
+
unless datum[:path][0, 1] == '/'
|
207
|
+
datum[:path].insert(0, '/')
|
84
208
|
end
|
85
209
|
|
86
210
|
if block_given?
|
87
211
|
$stderr.puts("Excon requests with a block are deprecated, pass :response_block instead (#{caller.first})")
|
88
|
-
|
212
|
+
datum[:response_block] = Proc.new
|
89
213
|
end
|
90
214
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
response = params[:instrumentor].instrument(event_name, params) do
|
98
|
-
request_kernel(params)
|
99
|
-
end
|
100
|
-
params[:instrumentor].instrument("#{params[:instrumentor_name]}.response", response.attributes)
|
101
|
-
response
|
102
|
-
else
|
103
|
-
request_kernel(params)
|
215
|
+
datum[:middlewares] = [
|
216
|
+
lambda {|app| Excon::Middleware::Instrumentor.new(app) },
|
217
|
+
lambda {|app| Excon::Middleware::Expects.new(app) }
|
218
|
+
]
|
219
|
+
stack = datum[:middlewares].reverse.inject(self) do |middlewares, middleware|
|
220
|
+
middleware.call(middlewares)
|
104
221
|
end
|
222
|
+
response_datum = stack.call(datum)
|
223
|
+
|
224
|
+
Excon::Response.new(response_datum)
|
105
225
|
rescue => request_error
|
106
|
-
if
|
226
|
+
if datum[:idempotent] && [Excon::Errors::Timeout, Excon::Errors::SocketError,
|
107
227
|
Excon::Errors::HTTPStatusError].any? {|ex| request_error.kind_of? ex }
|
108
|
-
retries_remaining
|
109
|
-
retries_remaining
|
110
|
-
|
111
|
-
retry
|
228
|
+
datum[:retries_remaining] -= 1
|
229
|
+
if datum[:retries_remaining] > 0
|
230
|
+
request(datum, &block)
|
112
231
|
else
|
113
|
-
if
|
114
|
-
|
232
|
+
if datum.has_key?(:instrumentor)
|
233
|
+
datum[:instrumentor].instrument("#{datum[:instrumentor_name]}.error", :error => request_error)
|
115
234
|
end
|
116
235
|
raise(request_error)
|
117
236
|
end
|
118
237
|
else
|
119
|
-
if
|
120
|
-
|
238
|
+
if datum.has_key?(:instrumentor)
|
239
|
+
datum[:instrumentor].instrument("#{datum[:instrumentor_name]}.error", :error => request_error)
|
121
240
|
end
|
241
|
+
reset
|
122
242
|
raise(request_error)
|
123
243
|
end
|
124
244
|
end
|
@@ -138,22 +258,22 @@ module Excon
|
|
138
258
|
|
139
259
|
def retry_limit=(new_retry_limit)
|
140
260
|
$stderr.puts("Excon::Connection#retry_limit= is deprecated, pass :retry_limit to the initializer (#{caller.first})")
|
141
|
-
@
|
261
|
+
@data[:retry_limit] = new_retry_limit
|
142
262
|
end
|
143
263
|
|
144
264
|
def retry_limit
|
145
265
|
$stderr.puts("Excon::Connection#retry_limit is deprecated, pass :retry_limit to the initializer (#{caller.first})")
|
146
|
-
@
|
266
|
+
@data[:retry_limit] ||= DEFAULT_RETRY_LIMIT
|
147
267
|
end
|
148
268
|
|
149
269
|
def inspect
|
150
270
|
vars = instance_variables.inject({}) do |accum, var|
|
151
271
|
accum.merge!(var.to_sym => instance_variable_get(var))
|
152
272
|
end
|
153
|
-
if vars[:'@
|
154
|
-
vars[:'@
|
155
|
-
vars[:'@
|
156
|
-
vars[:'@
|
273
|
+
if vars[:'@data'][:headers].has_key?('Authorization')
|
274
|
+
vars[:'@data'] = vars[:'@data'].dup
|
275
|
+
vars[:'@data'][:headers] = vars[:'@data'][:headers].dup
|
276
|
+
vars[:'@data'][:headers]['Authorization'] = REDACTED
|
157
277
|
end
|
158
278
|
inspection = '#<Excon::Connection:'
|
159
279
|
inspection << (object_id << 1).to_s(16)
|
@@ -184,186 +304,74 @@ module Excon
|
|
184
304
|
end
|
185
305
|
end
|
186
306
|
|
187
|
-
def
|
188
|
-
begin
|
189
|
-
response = if params[:mock]
|
190
|
-
invoke_stub(params)
|
191
|
-
else
|
192
|
-
socket.params = params
|
193
|
-
# start with "METHOD /path"
|
194
|
-
request = params[:method].to_s.upcase << ' '
|
195
|
-
if @proxy
|
196
|
-
request << params[:scheme] << '://' << params[:host_port]
|
197
|
-
end
|
198
|
-
request << params[:path]
|
199
|
-
|
200
|
-
# add query to path, if there is one
|
201
|
-
case params[:query]
|
202
|
-
when String
|
203
|
-
request << '?' << params[:query]
|
204
|
-
when Hash
|
205
|
-
request << '?'
|
206
|
-
params[:query].each do |key, values|
|
207
|
-
if values.nil?
|
208
|
-
request << key.to_s << '&'
|
209
|
-
else
|
210
|
-
[*values].each do |value|
|
211
|
-
request << key.to_s << '=' << CGI.escape(value.to_s) << '&'
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
215
|
-
request.chop! # remove trailing '&'
|
216
|
-
end
|
217
|
-
|
218
|
-
# finish first line with "HTTP/1.1\r\n"
|
219
|
-
request << HTTP_1_1
|
220
|
-
|
221
|
-
if params.has_key?(:request_block)
|
222
|
-
params[:headers]['Transfer-Encoding'] = 'chunked'
|
223
|
-
elsif ! (params[:method].to_s.casecmp('GET') == 0 && params[:body].nil?)
|
224
|
-
# The HTTP spec isn't clear on it, but specifically, GET requests don't usually send bodies;
|
225
|
-
# if they don't, sending Content-Length:0 can cause issues.
|
226
|
-
params[:headers]['Content-Length'] = detect_content_length(params[:body])
|
227
|
-
end
|
228
|
-
|
229
|
-
# add headers to request
|
230
|
-
params[:headers].each do |key, values|
|
231
|
-
[*values].each do |value|
|
232
|
-
request << key.to_s << ': ' << value.to_s << CR_NL
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
# add additional "\r\n" to indicate end of headers
|
237
|
-
request << CR_NL
|
238
|
-
|
239
|
-
# write out the request, sans body
|
240
|
-
socket.write(request)
|
241
|
-
|
242
|
-
# write out the body
|
243
|
-
if params.has_key?(:request_block)
|
244
|
-
while true
|
245
|
-
chunk = params[:request_block].call
|
246
|
-
if FORCE_ENC
|
247
|
-
chunk.force_encoding('BINARY')
|
248
|
-
end
|
249
|
-
if chunk.length > 0
|
250
|
-
socket.write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
|
251
|
-
else
|
252
|
-
socket.write('0' << CR_NL << CR_NL)
|
253
|
-
break
|
254
|
-
end
|
255
|
-
end
|
256
|
-
elsif !params[:body].nil?
|
257
|
-
if params[:body].is_a?(String)
|
258
|
-
unless params[:body].empty?
|
259
|
-
socket.write(params[:body])
|
260
|
-
end
|
261
|
-
else
|
262
|
-
if params[:body].respond_to?(:binmode)
|
263
|
-
params[:body].binmode
|
264
|
-
end
|
265
|
-
if params[:body].respond_to?(:pos=)
|
266
|
-
params[:body].pos = 0
|
267
|
-
end
|
268
|
-
while chunk = params[:body].read(params[:chunk_size])
|
269
|
-
socket.write(chunk)
|
270
|
-
end
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
# read the response
|
275
|
-
response = Excon::Response.parse(socket, params)
|
276
|
-
|
277
|
-
if response.headers['Connection'] == 'close'
|
278
|
-
reset
|
279
|
-
end
|
280
|
-
|
281
|
-
response
|
282
|
-
end
|
283
|
-
rescue Excon::Errors::StubNotFound, Excon::Errors::Timeout => error
|
284
|
-
raise(error)
|
285
|
-
rescue => socket_error
|
286
|
-
reset
|
287
|
-
raise(Excon::Errors::SocketError.new(socket_error))
|
288
|
-
end
|
289
|
-
|
290
|
-
if params.has_key?(:expects) && ![*params[:expects]].include?(response.status)
|
291
|
-
reset
|
292
|
-
raise(Excon::Errors.status_error(params, response))
|
293
|
-
else
|
294
|
-
response
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
def invoke_stub(params)
|
299
|
-
|
307
|
+
def invoke_stub(datum)
|
300
308
|
# convert File/Tempfile body to string before matching:
|
301
|
-
unless
|
302
|
-
if
|
303
|
-
|
309
|
+
unless datum[:body].nil? || datum[:body].is_a?(String)
|
310
|
+
if datum[:body].respond_to?(:binmode)
|
311
|
+
datum[:body].binmode
|
304
312
|
end
|
305
|
-
if
|
306
|
-
|
313
|
+
if datum[:body].respond_to?(:rewind)
|
314
|
+
datum[:body].rewind
|
307
315
|
end
|
308
|
-
|
316
|
+
datum[:body] = datum[:body].read
|
309
317
|
end
|
310
318
|
|
311
|
-
|
319
|
+
datum[:captures] = {:headers => {}} # setup data to hold captures
|
312
320
|
Excon.stubs.each do |stub, response|
|
313
321
|
headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
|
314
322
|
case value = stub[:headers][key]
|
315
323
|
when Regexp
|
316
|
-
if match = value.match(
|
317
|
-
|
324
|
+
if match = value.match(datum[:headers][key])
|
325
|
+
datum[:captures][:headers][key] = match.captures
|
318
326
|
end
|
319
327
|
match
|
320
328
|
else
|
321
|
-
value ==
|
329
|
+
value == datum[:headers][key]
|
322
330
|
end
|
323
331
|
end
|
324
332
|
non_headers_match = (stub.keys - [:headers]).all? do |key|
|
325
333
|
case value = stub[key]
|
326
334
|
when Regexp
|
327
|
-
if match = value.match(
|
328
|
-
|
335
|
+
if match = value.match(datum[key])
|
336
|
+
datum[:captures][key] = match.captures
|
329
337
|
end
|
330
338
|
match
|
331
339
|
else
|
332
|
-
value ==
|
340
|
+
value == datum[key]
|
333
341
|
end
|
334
342
|
end
|
335
343
|
if headers_match && non_headers_match
|
336
|
-
|
344
|
+
response_datum = case response
|
337
345
|
when Proc
|
338
|
-
response.call(
|
346
|
+
response.call(datum)
|
339
347
|
else
|
340
348
|
response
|
341
349
|
end
|
342
350
|
|
343
|
-
if
|
351
|
+
if datum[:expects] && ![*datum[:expects]].include?(response_datum[:status])
|
344
352
|
# don't pass stuff into a block if there was an error
|
345
|
-
elsif
|
346
|
-
body =
|
353
|
+
elsif datum.has_key?(:response_block) && response_datum.has_key?(:body)
|
354
|
+
body = response_datum.delete(:body)
|
347
355
|
content_length = remaining = body.bytesize
|
348
356
|
i = 0
|
349
357
|
while i < body.length
|
350
|
-
|
351
|
-
remaining -=
|
352
|
-
i +=
|
358
|
+
datum[:response_block].call(body[i, datum[:chunk_size]], [remaining - datum[:chunk_size], 0].max, content_length)
|
359
|
+
remaining -= datum[:chunk_size]
|
360
|
+
i += datum[:chunk_size]
|
353
361
|
end
|
354
362
|
end
|
355
|
-
return
|
363
|
+
return response_datum
|
356
364
|
end
|
357
365
|
end
|
358
366
|
# if we reach here no stubs matched
|
359
|
-
raise(Excon::Errors::StubNotFound.new('no stubs matched ' <<
|
367
|
+
raise(Excon::Errors::StubNotFound.new('no stubs matched ' << datum.inspect))
|
360
368
|
end
|
361
369
|
|
362
370
|
def socket
|
363
|
-
sockets[@socket_key] ||= if @
|
364
|
-
Excon::SSLSocket.new(@
|
371
|
+
sockets[@socket_key] ||= if @data[:scheme] == HTTPS
|
372
|
+
Excon::SSLSocket.new(@data)
|
365
373
|
else
|
366
|
-
Excon::Socket.new(@
|
374
|
+
Excon::Socket.new(@data)
|
367
375
|
end
|
368
376
|
end
|
369
377
|
|
@@ -372,18 +380,23 @@ module Excon
|
|
372
380
|
end
|
373
381
|
|
374
382
|
def setup_proxy(proxy)
|
375
|
-
|
376
|
-
|
377
|
-
|
383
|
+
case proxy
|
384
|
+
when String
|
385
|
+
uri = URI.parse(proxy)
|
386
|
+
unless uri.host and uri.port and uri.scheme
|
387
|
+
raise Excon::Errors::ProxyParseError, "Proxy is invalid"
|
388
|
+
end
|
389
|
+
{
|
390
|
+
:host => uri.host,
|
391
|
+
:host_port => '' << uri.host << ':' << uri.port.to_s,
|
392
|
+
:password => uri.password,
|
393
|
+
:port => uri.port,
|
394
|
+
:scheme => uri.scheme,
|
395
|
+
:user => uri.user
|
396
|
+
}
|
397
|
+
else
|
398
|
+
proxy
|
378
399
|
end
|
379
|
-
{
|
380
|
-
:host => uri.host,
|
381
|
-
:host_port => '' << uri.host << ':' << uri.port.to_s,
|
382
|
-
:password => uri.password,
|
383
|
-
:port => uri.port,
|
384
|
-
:scheme => uri.scheme,
|
385
|
-
:user => uri.user
|
386
|
-
}
|
387
400
|
end
|
388
401
|
|
389
402
|
end
|