excon 0.30.0 → 0.31.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 +68 -1
- data/changelog.txt +14 -0
- data/excon.gemspec +5 -3
- data/lib/excon.rb +2 -2
- data/lib/excon/connection.rb +19 -8
- data/lib/excon/constants.rb +2 -1
- data/lib/excon/errors.rb +48 -41
- data/lib/excon/utils.rb +15 -3
- data/tests/authorization_header_tests.rb +7 -6
- data/tests/basic_tests.rb +1 -2
- data/tests/header_tests.rb +5 -2
- data/tests/middlewares/canned_response_tests.rb +5 -8
- data/tests/middlewares/decompress_tests.rb +34 -27
- data/tests/middlewares/instrumentation_tests.rb +11 -10
- data/tests/middlewares/mock_tests.rb +50 -48
- data/tests/middlewares/redirect_follower_tests.rb +47 -49
- data/tests/pipeline_tests.rb +40 -0
- data/tests/proxy_tests.rb +23 -18
- data/tests/query_string_tests.rb +16 -16
- data/tests/rackups/basic.rb +4 -0
- data/tests/rackups/proxy.ru +4 -0
- data/tests/rackups/query_string.ru +4 -0
- data/tests/rackups/request_headers.ru +4 -0
- data/tests/rackups/request_methods.ru +6 -2
- data/tests/rackups/response_header.ru +4 -0
- data/tests/rackups/thread_safety.ru +4 -0
- data/tests/rackups/timeout.ru +5 -0
- data/tests/rackups/webrick_patch.rb +34 -0
- data/tests/request_method_tests.rb +2 -2
- data/tests/request_tests.rb +113 -0
- data/tests/response_tests.rb +20 -14
- data/tests/servers/good.rb +38 -21
- data/tests/test_helper.rb +43 -23
- data/tests/thread_safety_tests.rb +3 -3
- data/tests/timeout_tests.rb +2 -3
- metadata +6 -4
- data/tests/requests_tests.rb +0 -73
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -45,6 +45,41 @@ post_response = connection.post(:path => '/foo')
|
|
45
45
|
delete_response = connection.delete(:path => '/bar')
|
46
46
|
```
|
47
47
|
|
48
|
+
By default, each connection is non-persistent. This means that each request made against a connection behaves like a
|
49
|
+
one-off request. Each request will establish a socket connection to the server, then close the socket once the request
|
50
|
+
is complete.
|
51
|
+
|
52
|
+
To use a persistent connection, use the `:persistent` option:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
connection = Excon.new('http://geemus.com', :persistent => true)
|
56
|
+
```
|
57
|
+
|
58
|
+
The initial request will establish a socket connection to the server and leave the socket open. Subsequent requests
|
59
|
+
will reuse that socket. You may call `Connection#reset` at any time to close the underlying socket, and the next request
|
60
|
+
will establish a new socket connection.
|
61
|
+
|
62
|
+
You may also control persistence on a per-request basis by setting the `:persistent` option for each request.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
connection = Excon.new('http://geemus.com') # non-persistent by default
|
66
|
+
connection.get # socket established, then closed
|
67
|
+
connection.get(:persistent => true) # socket established, left open
|
68
|
+
connection.get(:persistent => true) # socket reused
|
69
|
+
connection.get # socket reused, then closed
|
70
|
+
|
71
|
+
connection = Excon.new('http://geemus.com', :persistent => true)
|
72
|
+
connection.get # socket established, left open
|
73
|
+
connection.get(:persistent => false) # socket reused, then closed
|
74
|
+
connection.get(:persistent => false) # socket established, then closed
|
75
|
+
connection.get # socket established, left open
|
76
|
+
connection.get # socket reused
|
77
|
+
```
|
78
|
+
|
79
|
+
Note that sending a request with `:persistent => false` to close the socket will also send `Connection: close` to inform
|
80
|
+
the server the connection is no longer needed. `Connection#reset` will simply close our end of the socket.
|
81
|
+
|
82
|
+
|
48
83
|
Options
|
49
84
|
-------
|
50
85
|
|
@@ -131,13 +166,16 @@ Iterating in this way allows you to have more granular control over writes and t
|
|
131
166
|
Pipelining Requests
|
132
167
|
------------------
|
133
168
|
|
134
|
-
You can make use of HTTP pipelining to improve performance. Instead of the normal request/response
|
169
|
+
You can make use of HTTP pipelining to improve performance. Instead of the normal request/response cycle, pipelining sends a series of requests and then receives a series of responses. You can take advantage of this using the `requests` method, which takes an array of params where each is a hash like request would receive and returns an array of responses.
|
135
170
|
|
136
171
|
```ruby
|
137
172
|
connection = Excon.new('http://geemus.com/')
|
138
173
|
connection.requests([{:method => :get}, {:method => :get}])
|
139
174
|
```
|
140
175
|
|
176
|
+
By default, each call to `requests` will use a separate persistent socket connection. To make multiple `requests` calls
|
177
|
+
using a single persistent connection, set `:persistent => true` when establishing the connection.
|
178
|
+
|
141
179
|
Streaming Responses
|
142
180
|
-------------------
|
143
181
|
|
@@ -307,6 +345,35 @@ Excon.defaults[:ssl_verify_peer] = false
|
|
307
345
|
|
308
346
|
Either of these should allow you to work around the socket error and continue with your work.
|
309
347
|
|
348
|
+
## Getting Help
|
349
|
+
|
350
|
+
* [General Documentation](http://excon.io).
|
351
|
+
* Ask specific questions on [Stack Overflow](http://stackoverflow.com/questions/tagged/excon).
|
352
|
+
* Report bugs and discuss potential features in [Github issues](https://github.com/geemus/excon/issues).
|
353
|
+
|
354
|
+
## Getting Involved
|
355
|
+
|
356
|
+
New contributors are always welcome, when it doubt please ask questions. We strive to be an open and welcoming community. Please be nice to one another.
|
357
|
+
|
358
|
+
### Coding
|
359
|
+
|
360
|
+
* Pick a task:
|
361
|
+
* Offer feedback on open [pull requests](https://github.com/geemus/excon/pulls).
|
362
|
+
* Review open [issues](https://github.com/geemus/excon/issues) for things to help on.
|
363
|
+
* [Create an issue](https://github.com/geemus/excon/issues/new) to start a discussion on additions or features.
|
364
|
+
* Fork the project, add your changes and tests to cover them in a topic branch.
|
365
|
+
* Commit your changes and rebase against `geemus/excon` to ensure everything is up to date.
|
366
|
+
* [Submit a pull request](https://github.com/geemus/excon/compare/).
|
367
|
+
|
368
|
+
### Non-Coding
|
369
|
+
|
370
|
+
* Offer feedback on open [issues](https://github.com/geemus/excon/issues).
|
371
|
+
* Write and help edit [documentation](https://github.com/geemus/excon.github.com).
|
372
|
+
* Translate [documentation](https://github.com/geemus/excon.github.com) in to other languages.
|
373
|
+
* Organize or volunteer at events.
|
374
|
+
* [Donate](https://www.gittip.com/geemus/)!
|
375
|
+
* Discuss other ideas for contribution with [geemus](mailto:geemus+excon@gmail.com).
|
376
|
+
|
310
377
|
Copyright
|
311
378
|
---------
|
312
379
|
|
data/changelog.txt
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
0.31.0 12/16/2013
|
2
|
+
=================
|
3
|
+
|
4
|
+
test fixes for Bundler.require usage and Rack::Lint
|
5
|
+
use production mode + dump errors for tests
|
6
|
+
use Utils in Excon.stub
|
7
|
+
add implementation of stuff from WEBrick directly to utils
|
8
|
+
update test server to send connection close and process buffer after response
|
9
|
+
add :persistent option, defaults true
|
10
|
+
group HTTP errors by type
|
11
|
+
patch to webrick to workaround intermitent test failures
|
12
|
+
only use Open4 for 1.8.7
|
13
|
+
update/expand getting help/getting involved in readme
|
14
|
+
|
1
15
|
0.30.0 11/25/2013
|
2
16
|
=================
|
3
17
|
|
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 = '2013-
|
16
|
+
s.version = '0.31.0'
|
17
|
+
s.date = '2013-12-16'
|
18
18
|
s.rubyforge_project = 'excon'
|
19
19
|
|
20
20
|
## Make sure your summary is short. The description may be as long
|
@@ -125,6 +125,7 @@ Gem::Specification.new do |s|
|
|
125
125
|
tests/middlewares/instrumentation_tests.rb
|
126
126
|
tests/middlewares/mock_tests.rb
|
127
127
|
tests/middlewares/redirect_follower_tests.rb
|
128
|
+
tests/pipeline_tests.rb
|
128
129
|
tests/proxy_tests.rb
|
129
130
|
tests/query_string_tests.rb
|
130
131
|
tests/rackups/basic.rb
|
@@ -140,9 +141,10 @@ Gem::Specification.new do |s|
|
|
140
141
|
tests/rackups/ssl_verify_peer.ru
|
141
142
|
tests/rackups/thread_safety.ru
|
142
143
|
tests/rackups/timeout.ru
|
144
|
+
tests/rackups/webrick_patch.rb
|
143
145
|
tests/request_headers_tests.rb
|
144
146
|
tests/request_method_tests.rb
|
145
|
-
tests/
|
147
|
+
tests/request_tests.rb
|
146
148
|
tests/response_tests.rb
|
147
149
|
tests/servers/bad.rb
|
148
150
|
tests/servers/eof.rb
|
data/lib/excon.rb
CHANGED
@@ -10,7 +10,6 @@ require 'timeout'
|
|
10
10
|
require 'uri'
|
11
11
|
require 'zlib'
|
12
12
|
require 'stringio'
|
13
|
-
require 'webrick/httputils'
|
14
13
|
|
15
14
|
BasicSocket.do_not_reverse_lookup = true
|
16
15
|
|
@@ -41,6 +40,7 @@ module Excon
|
|
41
40
|
:mock => false,
|
42
41
|
:nonblock => true,
|
43
42
|
:omit_default_port => false,
|
43
|
+
:persistent => false,
|
44
44
|
:read_timeout => 60,
|
45
45
|
:retry_limit => DEFAULT_RETRY_LIMIT,
|
46
46
|
:ssl_verify_peer => true,
|
@@ -166,7 +166,7 @@ module Excon
|
|
166
166
|
)
|
167
167
|
if uri.user || uri.password
|
168
168
|
request_params[:headers] ||= {}
|
169
|
-
user, pass =
|
169
|
+
user, pass = Utils.unescape_form(uri.user.to_s), Utils.unescape_form(uri.password.to_s)
|
170
170
|
request_params[:headers]['Authorization'] ||= 'Basic ' << ['' << user << ':' << pass].pack('m').delete(Excon::CR_NL)
|
171
171
|
end
|
172
172
|
end
|
data/lib/excon/connection.rb
CHANGED
@@ -152,7 +152,7 @@ module Excon
|
|
152
152
|
else
|
153
153
|
datum[:headers]['TE'] = 'trailers, deflate, gzip'
|
154
154
|
end
|
155
|
-
datum[:headers]['Connection'] = 'TE'
|
155
|
+
datum[:headers]['Connection'] = datum[:persistent] ? 'TE' : 'TE, close'
|
156
156
|
|
157
157
|
# add headers to request
|
158
158
|
datum[:headers].each do |key, values|
|
@@ -267,10 +267,14 @@ module Excon
|
|
267
267
|
unless datum[:pipeline]
|
268
268
|
datum = response(datum)
|
269
269
|
|
270
|
-
if
|
271
|
-
if
|
272
|
-
|
270
|
+
if datum[:persistent]
|
271
|
+
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
|
272
|
+
if split_header_value(datum[:response][:headers][key]).any? {|t| t.casecmp('close') }
|
273
|
+
reset
|
274
|
+
end
|
273
275
|
end
|
276
|
+
else
|
277
|
+
reset
|
274
278
|
end
|
275
279
|
|
276
280
|
Excon::Response.new(datum[:response])
|
@@ -290,16 +294,23 @@ module Excon
|
|
290
294
|
# Sends the supplied requests to the destination host using pipelining.
|
291
295
|
# @pipeline_params [Array<Hash>] pipeline_params An array of one or more optional params, override defaults set in Connection.new, see #request for details
|
292
296
|
def requests(pipeline_params)
|
297
|
+
pipeline_params.each {|params| params.merge!(:pipeline => true, :persistent => true) }
|
298
|
+
pipeline_params.last.merge!(:persistent => @data[:persistent])
|
299
|
+
|
293
300
|
responses = pipeline_params.map do |params|
|
294
|
-
request(params
|
301
|
+
request(params)
|
295
302
|
end.map do |datum|
|
296
303
|
Excon::Response.new(response(datum)[:response])
|
297
304
|
end
|
298
305
|
|
299
|
-
if
|
300
|
-
if
|
301
|
-
|
306
|
+
if @data[:persistent]
|
307
|
+
if key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
|
308
|
+
if split_header_value(responses.last[:headers][key]).any? {|t| t.casecmp('close') }
|
309
|
+
reset
|
310
|
+
end
|
302
311
|
end
|
312
|
+
else
|
313
|
+
reset
|
303
314
|
end
|
304
315
|
|
305
316
|
responses
|
data/lib/excon/constants.rb
CHANGED
data/lib/excon/errors.rb
CHANGED
@@ -35,47 +35,54 @@ module Excon
|
|
35
35
|
@response = response
|
36
36
|
end
|
37
37
|
end
|
38
|
-
|
39
|
-
|
40
|
-
class
|
41
|
-
class
|
42
|
-
class
|
43
|
-
class
|
44
|
-
class
|
45
|
-
|
46
|
-
class
|
47
|
-
class
|
48
|
-
class
|
49
|
-
class
|
50
|
-
class
|
51
|
-
class
|
52
|
-
class
|
53
|
-
class
|
54
|
-
class
|
55
|
-
class
|
56
|
-
class
|
57
|
-
class
|
58
|
-
class
|
59
|
-
class
|
60
|
-
class
|
61
|
-
class
|
62
|
-
class
|
63
|
-
class
|
64
|
-
class
|
65
|
-
class
|
66
|
-
class
|
67
|
-
class
|
68
|
-
class
|
69
|
-
class
|
70
|
-
class
|
71
|
-
class
|
72
|
-
class
|
73
|
-
class
|
74
|
-
class
|
75
|
-
class
|
76
|
-
class
|
77
|
-
class
|
78
|
-
class
|
38
|
+
|
39
|
+
# HTTP Error classes
|
40
|
+
class Informational < HTTPStatusError; end
|
41
|
+
class Success < HTTPStatusError; end
|
42
|
+
class Redirection < HTTPStatusError; end
|
43
|
+
class ClientError < HTTPStatusError; end
|
44
|
+
class ServerError < HTTPStatusError; end
|
45
|
+
|
46
|
+
class Continue < Informational; end # 100
|
47
|
+
class SwitchingProtocols < Informational; end # 101
|
48
|
+
class OK < Success; end # 200
|
49
|
+
class Created < Success; end # 201
|
50
|
+
class Accepted < Success; end # 202
|
51
|
+
class NonAuthoritativeInformation < Success; end # 203
|
52
|
+
class NoContent < Success; end # 204
|
53
|
+
class ResetContent < Success; end # 205
|
54
|
+
class PartialContent < Success; end # 206
|
55
|
+
class MultipleChoices < Redirection; end # 300
|
56
|
+
class MovedPermanently < Redirection; end # 301
|
57
|
+
class Found < Redirection; end # 302
|
58
|
+
class SeeOther < Redirection; end # 303
|
59
|
+
class NotModified < Redirection; end # 304
|
60
|
+
class UseProxy < Redirection; end # 305
|
61
|
+
class TemporaryRedirect < Redirection; end # 307
|
62
|
+
class BadRequest < ClientError; end # 400
|
63
|
+
class Unauthorized < ClientError; end # 401
|
64
|
+
class PaymentRequired < ClientError; end # 402
|
65
|
+
class Forbidden < ClientError; end # 403
|
66
|
+
class NotFound < ClientError; end # 404
|
67
|
+
class MethodNotAllowed < ClientError; end # 405
|
68
|
+
class NotAcceptable < ClientError; end # 406
|
69
|
+
class ProxyAuthenticationRequired < ClientError; end # 407
|
70
|
+
class RequestTimeout < ClientError; end # 408
|
71
|
+
class Conflict < ClientError; end # 409
|
72
|
+
class Gone < ClientError; end # 410
|
73
|
+
class LengthRequired < ClientError; end # 411
|
74
|
+
class PreconditionFailed < ClientError; end # 412
|
75
|
+
class RequestEntityTooLarge < ClientError; end # 413
|
76
|
+
class RequestURITooLong < ClientError; end # 414
|
77
|
+
class UnsupportedMediaType < ClientError; end # 415
|
78
|
+
class RequestedRangeNotSatisfiable < ClientError; end # 416
|
79
|
+
class ExpectationFailed < ClientError; end # 417
|
80
|
+
class UnprocessableEntity < ClientError; end # 422
|
81
|
+
class InternalServerError < ServerError; end # 500
|
82
|
+
class NotImplemented < ServerError; end # 501
|
83
|
+
class BadGateway < ServerError; end # 502
|
84
|
+
class ServiceUnavailable < ServerError; end # 503
|
85
|
+
class GatewayTimeout < ServerError; end # 504
|
79
86
|
|
80
87
|
# Messages for nicer exceptions, from rfc2616
|
81
88
|
def self.status_error(request, response)
|
data/lib/excon/utils.rb
CHANGED
@@ -2,6 +2,8 @@ module Excon
|
|
2
2
|
module Utils
|
3
3
|
extend self
|
4
4
|
|
5
|
+
ESCAPED = /%([0-9a-fA-F]{2})/
|
6
|
+
|
5
7
|
def valid_connection_keys(params = {})
|
6
8
|
Excon::VALID_CONNECTION_KEYS
|
7
9
|
end
|
@@ -57,17 +59,27 @@ module Excon
|
|
57
59
|
# Splits a header value +str+ according to HTTP specification.
|
58
60
|
def split_header_value(str)
|
59
61
|
return [] if str.nil?
|
60
|
-
|
62
|
+
str = str.strip
|
63
|
+
str.force_encoding('BINARY') if FORCE_ENC
|
64
|
+
str.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]+)+)
|
65
|
+
(?:,\s*|\Z)'xn).flatten
|
61
66
|
end
|
62
67
|
|
63
68
|
# Unescapes HTTP reserved and unwise characters in +str+
|
64
69
|
def unescape_uri(str)
|
65
|
-
|
70
|
+
str = str.dup
|
71
|
+
str.force_encoding('BINARY') if FORCE_ENC
|
72
|
+
str.gsub!(ESCAPED) { $1.hex.chr }
|
73
|
+
str
|
66
74
|
end
|
67
75
|
|
68
76
|
# Unescape form encoded values in +str+
|
69
77
|
def unescape_form(str)
|
70
|
-
|
78
|
+
str = str.dup
|
79
|
+
str.force_encoding('BINARY') if FORCE_ENC
|
80
|
+
str.gsub!(/\+/, ' ')
|
81
|
+
str.gsub!(ESCAPED) { $1.hex.chr }
|
82
|
+
str
|
71
83
|
end
|
72
84
|
end
|
73
85
|
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
Shindo.tests('Excon basics (Authorization data redacted)') do
|
2
2
|
with_rackup('basic_auth.ru') do
|
3
3
|
cases = [
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
['user & pass', 'http://user1:pass1@foo.com/', 'Basic dXNlcjE6cGFzczE='],
|
5
|
+
['email & pass', 'http://foo%40bar.com:pass1@foo.com/', 'Basic Zm9vQGJhci5jb206cGFzczE='],
|
6
|
+
['user no pass', 'http://three_user@foo.com/', 'Basic dGhyZWVfdXNlcjo='],
|
7
|
+
['pass no user', 'http://:derppass@foo.com/', 'Basic OmRlcnBwYXNz']
|
8
|
+
]
|
9
9
|
cases.each do |desc,url,auth_header|
|
10
|
-
conn =
|
10
|
+
conn = nil
|
11
11
|
|
12
12
|
test("authorization header concealed for #{desc}") do
|
13
|
+
conn = Excon.new(url)
|
13
14
|
!conn.inspect.include?(auth_header)
|
14
15
|
end
|
15
16
|
|
data/tests/basic_tests.rb
CHANGED
@@ -61,8 +61,7 @@ Shindo.tests('Excon basics (ssl file)',['focus']) do
|
|
61
61
|
|
62
62
|
basic_tests('https://127.0.0.1:8443',
|
63
63
|
:client_key => File.join(File.dirname(__FILE__), 'data', 'excon.cert.key'),
|
64
|
-
:client_cert => File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt')
|
65
|
-
:reset_connection => RUBY_VERSION == '1.9.2'
|
64
|
+
:client_cert => File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt')
|
66
65
|
)
|
67
66
|
|
68
67
|
end
|
data/tests/header_tests.rb
CHANGED
@@ -4,12 +4,15 @@ Shindo.tests('Excon response header support') do
|
|
4
4
|
with_rackup('response_header.ru') do
|
5
5
|
|
6
6
|
tests('Response#get_header') do
|
7
|
-
connection =
|
8
|
-
response =
|
7
|
+
connection = nil
|
8
|
+
response = nil
|
9
9
|
|
10
10
|
tests('with variable header capitalization') do
|
11
11
|
|
12
12
|
tests('response.get_header("mixedcase-header")').returns('MixedCase') do
|
13
|
+
connection = Excon.new('http://foo.com:8080', :proxy => 'http://127.0.0.1:9292')
|
14
|
+
response = connection.request(:method => :get, :path => '/foo')
|
15
|
+
|
13
16
|
response.get_header("mixedcase-header")
|
14
17
|
end
|
15
18
|
|
@@ -12,15 +12,12 @@ Shindo.tests("Excon support for middlewares that return canned responses") do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
connection = Excon.new(
|
16
|
-
'http://some-host.com/some-path',
|
17
|
-
:method => :get,
|
18
|
-
:middlewares => [canned_response_middleware] + Excon.defaults[:middlewares],
|
19
|
-
:response_block => Proc.new { } # to force streaming
|
20
|
-
)
|
21
|
-
|
22
15
|
tests('does not mutate the canned response body').returns("canned") do
|
23
|
-
|
16
|
+
Excon.get(
|
17
|
+
'http://some-host.com/some-path',
|
18
|
+
:middlewares => [canned_response_middleware] + Excon.defaults[:middlewares],
|
19
|
+
:response_block => Proc.new { } # to force streaming
|
20
|
+
)
|
24
21
|
the_body
|
25
22
|
end
|
26
23
|
end
|