excon 0.43.0 → 0.44.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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +9 -8
- data/LICENSE.md +1 -1
- data/README.md +4 -0
- data/Rakefile +1 -1
- data/changelog.txt +10 -0
- data/excon.gemspec +5 -3
- data/lib/excon.rb +7 -4
- data/lib/excon/connection.rb +9 -1
- data/lib/excon/constants.rb +2 -1
- data/lib/excon/errors.rb +2 -0
- data/lib/excon/extensions/uri.rb +33 -0
- data/lib/excon/middlewares/redirect_follower.rb +1 -0
- data/lib/excon/socket.rb +170 -109
- data/lib/excon/ssl_socket.rb +22 -24
- data/lib/excon/utils.rb +3 -9
- data/tests/basic_tests.rb +3 -0
- data/tests/middlewares/escape_path_tests.rb +2 -0
- data/tests/request_tests.rb +42 -39
- data/tests/servers/good.rb +1 -0
- data/tests/utils_tests.rb +13 -0
- metadata +19 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90759d86297bebbce2b0374cb730466df92321ec
|
4
|
+
data.tar.gz: 67e4b85deb2f2b52455b190fa4c3df120fb1c532
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bda8264156cb7f3983aaac5cd8327254cc0f06a1ec9f1dc701271798cf06bfe2c207e14fb55bdd9b6deb96ce15843c0b2c6b0f7a5fbb66ead49a21d199b9f976
|
7
|
+
data.tar.gz: 511d4b47dd504535f78a25c4a741fcaea3206ec466fe57c199c6ae8c4ee8d5111ca5a1f8a39cb3e80cfb6f25d7f226df74a2b748f4b2392e3ece823479e470e8
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
excon (0.
|
4
|
+
excon (0.44.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://rubygems.org/
|
@@ -13,20 +13,20 @@ GEM
|
|
13
13
|
chronic (0.6.7)
|
14
14
|
delorean (2.0.0)
|
15
15
|
chronic
|
16
|
-
eventmachine (1.0.
|
17
|
-
eventmachine (1.0.
|
16
|
+
eventmachine (1.0.4)
|
17
|
+
eventmachine (1.0.4-java)
|
18
18
|
ffi2-generators (0.1.1)
|
19
19
|
formatador (0.2.3)
|
20
20
|
i18n (0.6.0)
|
21
21
|
jruby-openssl (0.8.8)
|
22
22
|
bouncy-castle-java (>= 1.5.0147)
|
23
|
-
json (1.
|
24
|
-
json (1.
|
23
|
+
json (1.8.2)
|
24
|
+
json (1.8.2-java)
|
25
25
|
kgio (2.9.2)
|
26
26
|
minitest (4.7.5)
|
27
27
|
multi_json (1.3.6)
|
28
28
|
open4 (1.3.0)
|
29
|
-
rack (1.
|
29
|
+
rack (1.6.0)
|
30
30
|
rack-protection (1.2.0)
|
31
31
|
rack
|
32
32
|
raindrops (0.13.0)
|
@@ -257,11 +257,12 @@ PLATFORMS
|
|
257
257
|
DEPENDENCIES
|
258
258
|
activesupport
|
259
259
|
delorean
|
260
|
-
eventmachine
|
260
|
+
eventmachine (>= 1.0.4)
|
261
261
|
excon!
|
262
262
|
jruby-openssl
|
263
|
+
json (>= 1.8.2)
|
263
264
|
open4
|
264
|
-
rack (~> 1.
|
265
|
+
rack (~> 1.6)
|
265
266
|
rake
|
266
267
|
rdoc
|
267
268
|
rubysl (~> 2.0)
|
data/LICENSE.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2009-
|
3
|
+
Copyright (c) 2009-2015 [CONTRIBUTORS.md](https://github.com/excon/excon/blob/master/CONTRIBUTORS.md)
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
6
|
this software and associated documentation files (the "Software"), to deal in
|
data/README.md
CHANGED
@@ -145,6 +145,10 @@ connection = Excon.new('http://geemus.com/', :nonblock => false)
|
|
145
145
|
connection = Excon.new('http://username:password@secure.geemus.com')
|
146
146
|
connection = Excon.new('http://secure.geemus.com',
|
147
147
|
:user => 'username', :password => 'password')
|
148
|
+
|
149
|
+
# use custom uri parser
|
150
|
+
require 'addressable/uri'
|
151
|
+
connection = Excon.new('http://geemus.com/', uri_parser: Addressable::URI)
|
148
152
|
```
|
149
153
|
|
150
154
|
## Chunked Requests
|
data/Rakefile
CHANGED
data/changelog.txt
CHANGED
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 = '2015-01-
|
16
|
+
s.version = '0.44.0'
|
17
|
+
s.date = '2015-01-30'
|
18
18
|
s.rubyforge_project = 'excon'
|
19
19
|
|
20
20
|
## Make sure your summary is short. The description may be as long
|
@@ -56,12 +56,13 @@ Gem::Specification.new do |s|
|
|
56
56
|
# s.add_development_dependency('DEVDEPNAME', [">= 1.1.0", "< 2.0.0"])
|
57
57
|
s.add_development_dependency('activesupport')
|
58
58
|
s.add_development_dependency('delorean')
|
59
|
-
s.add_development_dependency('eventmachine')
|
59
|
+
s.add_development_dependency('eventmachine', '>= 1.0.4')
|
60
60
|
s.add_development_dependency('open4')
|
61
61
|
s.add_development_dependency('rake')
|
62
62
|
s.add_development_dependency('rdoc')
|
63
63
|
s.add_development_dependency('shindo')
|
64
64
|
s.add_development_dependency('sinatra')
|
65
|
+
s.add_development_dependency('json', '>= 1.8.2')
|
65
66
|
|
66
67
|
## Leave this section as-is. It will be automatically generated from the
|
67
68
|
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
@@ -100,6 +101,7 @@ Gem::Specification.new do |s|
|
|
100
101
|
lib/excon/connection.rb
|
101
102
|
lib/excon/constants.rb
|
102
103
|
lib/excon/errors.rb
|
104
|
+
lib/excon/extensions/uri.rb
|
103
105
|
lib/excon/headers.rb
|
104
106
|
lib/excon/middlewares/base.rb
|
105
107
|
lib/excon/middlewares/decompress.rb
|
data/lib/excon.rb
CHANGED
@@ -11,6 +11,8 @@ require 'uri'
|
|
11
11
|
require 'zlib'
|
12
12
|
require 'stringio'
|
13
13
|
|
14
|
+
require 'excon/extensions/uri'
|
15
|
+
|
14
16
|
require 'excon/middlewares/base'
|
15
17
|
require 'excon/middlewares/expects'
|
16
18
|
require 'excon/middlewares/idempotent'
|
@@ -50,8 +52,8 @@ module Excon
|
|
50
52
|
end
|
51
53
|
|
52
54
|
def display_warning(warning)
|
53
|
-
#
|
54
|
-
if
|
55
|
+
# Show warning if $VERBOSE or ENV['EXCON_DEBUG'] is set
|
56
|
+
if $VERBOSE || ENV['EXCON_DEBUG']
|
55
57
|
$stderr.puts '[excon][WARNING] ' << warning << "\n#{ caller.join("\n") }"
|
56
58
|
end
|
57
59
|
end
|
@@ -102,18 +104,19 @@ module Excon
|
|
102
104
|
# @param [Hash<Symbol, >] params One or more option params to set on the Connection instance
|
103
105
|
# @return [Connection] A new Excon::Connection instance
|
104
106
|
def new(url, params = {})
|
105
|
-
uri_parser = params[:uri_parser] ||
|
107
|
+
uri_parser = params[:uri_parser] || defaults[:uri_parser]
|
106
108
|
uri = uri_parser.parse(url)
|
107
109
|
unless uri.scheme
|
108
110
|
raise ArgumentError.new("Invalid URI: #{uri}")
|
109
111
|
end
|
110
112
|
params = {
|
111
113
|
:host => uri.host,
|
114
|
+
:hostname => uri.hostname,
|
112
115
|
:path => uri.path,
|
113
116
|
:port => uri.port,
|
114
117
|
:query => uri.query,
|
115
118
|
:scheme => uri.scheme
|
116
|
-
}.merge
|
119
|
+
}.merge(params)
|
117
120
|
if uri.password
|
118
121
|
params[:password] = Utils.unescape_uri(uri.password)
|
119
122
|
end
|
data/lib/excon/connection.rb
CHANGED
@@ -35,7 +35,8 @@ module Excon
|
|
35
35
|
# @param [Hash<Symbol, >] params One or more optional params
|
36
36
|
# @option params [String] :body Default text to be sent over a socket. Only used if :body absent in Connection#request params
|
37
37
|
# @option params [Hash<Symbol, String>] :headers The default headers to supply in a request. Only used if params[:headers] is not supplied to Connection#request
|
38
|
-
# @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String
|
38
|
+
# @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String. IPv6 addresses must be wrapped (e.g. [::1]). See URI#host.
|
39
|
+
# @option params [String] :hostname Same as host, but usable for socket connections. IPv6 addresses must not be wrapped (e.g. ::1). See URI#hostname.
|
39
40
|
# @option params [String] :path Default path; appears after 'scheme://host:port/'. Only used if params[:path] is not supplied to Connection#request
|
40
41
|
# @option params [Fixnum] :port The port on which to connect, to the destination host
|
41
42
|
# @option params [Hash] :query Default query; appended to the 'scheme://host:port/path/' in the form of '?key=value'. Will only be used if params[:query] is not supplied to Connection#request
|
@@ -358,6 +359,12 @@ module Excon
|
|
358
359
|
#params = params.dup
|
359
360
|
#invalid_keys.each {|key| params.delete(key) }
|
360
361
|
end
|
362
|
+
|
363
|
+
if validation == :connection && params.key?(:host) && !params.key?(:hostname)
|
364
|
+
Excon.display_warning('hostname is missing! For IPv6 support, provide both host and hostname: Excon::Connection#new(:host => uri.host, :hostname => uri.hostname, ...).')
|
365
|
+
params[:hostname] = params[:host]
|
366
|
+
end
|
367
|
+
|
361
368
|
params
|
362
369
|
end
|
363
370
|
|
@@ -421,6 +428,7 @@ module Excon
|
|
421
428
|
uri = @data[:proxy].is_a?(String) ? URI.parse(@data[:proxy]) : @data[:proxy]
|
422
429
|
@data[:proxy] = {
|
423
430
|
:host => uri.host,
|
431
|
+
:hostname => uri.hostname,
|
424
432
|
# path is only sensible for a Unix socket proxy
|
425
433
|
:path => uri.scheme == UNIX ? uri.path : nil,
|
426
434
|
:port => uri.port,
|
data/lib/excon/constants.rb
CHANGED
data/lib/excon/errors.rb
CHANGED
@@ -79,6 +79,7 @@ module Excon
|
|
79
79
|
class RequestedRangeNotSatisfiable < ClientError; end # 416
|
80
80
|
class ExpectationFailed < ClientError; end # 417
|
81
81
|
class UnprocessableEntity < ClientError; end # 422
|
82
|
+
class TooManyRequests < ClientError; end # 429
|
82
83
|
class InternalServerError < ServerError; end # 500
|
83
84
|
class NotImplemented < ServerError; end # 501
|
84
85
|
class BadGateway < ServerError; end # 502
|
@@ -123,6 +124,7 @@ module Excon
|
|
123
124
|
416 => [Excon::Errors::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
|
124
125
|
417 => [Excon::Errors::ExpectationFailed, 'Expectation Failed'],
|
125
126
|
422 => [Excon::Errors::UnprocessableEntity, 'Unprocessable Entity'],
|
127
|
+
429 => [Excon::Errors::TooManyRequests, 'Too Many Requests'],
|
126
128
|
500 => [Excon::Errors::InternalServerError, 'InternalServerError'],
|
127
129
|
501 => [Excon::Errors::NotImplemented, 'Not Implemented'],
|
128
130
|
502 => [Excon::Errors::BadGateway, 'Bad Gateway'],
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# TODO: Remove this monkey patch once ruby 1.9.3+ is the minimum supported version.
|
2
|
+
#
|
3
|
+
# This patch backports URI#hostname to ruby 1.9.2 and older.
|
4
|
+
# URI#hostname is used for IPv6 support in Excon.
|
5
|
+
#
|
6
|
+
# URI#hostname was added in stdlib in v1_9_3_0 in this commit:
|
7
|
+
# https://github.com/ruby/ruby/commit/5fd45a4b79dd26f9e7b6dc41142912df911e4d7d
|
8
|
+
#
|
9
|
+
# Addressable::URI is also an URI parser accepted in some parts of Excon.
|
10
|
+
# Addressable::URI#hostname was added in addressable-2.3.5+ in this commit:
|
11
|
+
# https://github.com/sporkmonger/addressable/commit/1b94abbec1f914d5f707c92a10efbb9e69aab65e
|
12
|
+
#
|
13
|
+
# Users who want to use Addressable::URI to parse URIs must upgrade to 2.3.5 or newer.
|
14
|
+
require 'uri'
|
15
|
+
unless URI("http://foo/bar").respond_to?(:hostname)
|
16
|
+
module URI
|
17
|
+
class Generic
|
18
|
+
# extract the host part of the URI and unwrap brackets for IPv6 addresses.
|
19
|
+
#
|
20
|
+
# This method is same as URI::Generic#host except
|
21
|
+
# brackets for IPv6 (and future IP) addresses are removed.
|
22
|
+
#
|
23
|
+
# u = URI("http://[::1]/bar")
|
24
|
+
# p u.hostname #=> "::1"
|
25
|
+
# p u.host #=> "[::1]"
|
26
|
+
#
|
27
|
+
def hostname
|
28
|
+
v = self.host
|
29
|
+
/\A\[(.*)\]\z/ =~ v ? $1 : v
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/excon/socket.rb
CHANGED
@@ -10,6 +10,7 @@ module Excon
|
|
10
10
|
Excon.display_warning('Excon::Socket#params is deprecated use Excon::Socket#data instead.')
|
11
11
|
@data
|
12
12
|
end
|
13
|
+
|
13
14
|
def params=(new_params)
|
14
15
|
Excon.display_warning('Excon::Socket#params= is deprecated use Excon::Socket#data= instead.')
|
15
16
|
@data = new_params
|
@@ -24,118 +25,59 @@ module Excon
|
|
24
25
|
@nonblock = data[:nonblock]
|
25
26
|
@read_buffer = ''
|
26
27
|
@eof = false
|
27
|
-
|
28
28
|
connect
|
29
29
|
end
|
30
30
|
|
31
|
-
def read(max_length=nil)
|
31
|
+
def read(max_length = nil)
|
32
32
|
if @eof
|
33
33
|
return max_length ? nil : ''
|
34
34
|
elsif @nonblock
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
else
|
41
|
-
while true
|
42
|
-
@read_buffer << @socket.read_nonblock(@data[:chunk_size])
|
43
|
-
end
|
44
|
-
end
|
45
|
-
rescue OpenSSL::SSL::SSLError => error
|
46
|
-
if error.message == 'read would block'
|
47
|
-
if IO.select([@socket], nil, nil, @data[:read_timeout])
|
48
|
-
retry
|
49
|
-
else
|
50
|
-
raise(Excon::Errors::Timeout.new("read timeout reached"))
|
51
|
-
end
|
52
|
-
else
|
53
|
-
raise(error)
|
54
|
-
end
|
55
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
56
|
-
if @read_buffer.empty?
|
57
|
-
# if we didn't read anything, try again...
|
58
|
-
if IO.select([@socket], nil, nil, @data[:read_timeout])
|
59
|
-
retry
|
60
|
-
else
|
61
|
-
raise(Excon::Errors::Timeout.new("read timeout reached"))
|
62
|
-
end
|
63
|
-
end
|
64
|
-
rescue EOFError
|
65
|
-
@eof = true
|
66
|
-
end
|
35
|
+
read_nonblock(max_length)
|
36
|
+
else
|
37
|
+
read_block(max_length)
|
38
|
+
end
|
39
|
+
end
|
67
40
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
41
|
+
def readline
|
42
|
+
return legacy_readline if RUBY_VERSION.to_f <= 1.8_7
|
43
|
+
begin
|
44
|
+
buffer = ''
|
45
|
+
buffer << @socket.read_nonblock(1) while buffer[-1] != "\n"
|
46
|
+
buffer
|
47
|
+
rescue Errno::EAGAIN
|
48
|
+
if timeout_reached('read')
|
49
|
+
raise_timeout_error('read')
|
74
50
|
else
|
75
|
-
|
76
|
-
@read_buffer.slice!(0, @read_buffer.length)
|
51
|
+
retry
|
77
52
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
53
|
+
rescue OpenSSL::SSL::SSLError => e
|
54
|
+
if e.message == 'read would block'
|
55
|
+
if timeout_reached('read')
|
56
|
+
raise_timeout_error('read')
|
57
|
+
else
|
58
|
+
retry
|
82
59
|
end
|
83
|
-
|
84
|
-
raise
|
60
|
+
else
|
61
|
+
raise(error)
|
85
62
|
end
|
86
63
|
end
|
87
64
|
end
|
88
65
|
|
89
|
-
def
|
90
|
-
|
66
|
+
def legacy_readline
|
67
|
+
begin
|
91
68
|
Timeout.timeout(@data[:read_timeout]) do
|
92
69
|
@socket.readline
|
93
|
-
|
70
|
+
end
|
94
71
|
rescue Timeout::Error
|
95
|
-
|
72
|
+
raise Excon::Errors::Timeout.new('read timeout reached')
|
96
73
|
end
|
97
74
|
end
|
98
75
|
|
99
76
|
def write(data)
|
100
77
|
if @nonblock
|
101
|
-
|
102
|
-
data.force_encoding('BINARY')
|
103
|
-
end
|
104
|
-
while true
|
105
|
-
written = nil
|
106
|
-
begin
|
107
|
-
# I wish that this API accepted a start position, then we wouldn't
|
108
|
-
# have to slice data when there is a short write.
|
109
|
-
written = @socket.write_nonblock(data)
|
110
|
-
rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
|
111
|
-
if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
|
112
|
-
raise error
|
113
|
-
else
|
114
|
-
if IO.select(nil, [@socket], nil, @data[:write_timeout])
|
115
|
-
retry
|
116
|
-
else
|
117
|
-
raise Excon::Errors::Timeout.new('write timeout reached')
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# Fast, common case.
|
123
|
-
break if written == data.size
|
124
|
-
|
125
|
-
# This takes advantage of the fact that most ruby implementations
|
126
|
-
# have Copy-On-Write strings. Thusly why requesting a subrange
|
127
|
-
# of data, we actually don't copy data because the new string
|
128
|
-
# simply references a subrange of the original.
|
129
|
-
data = data[written, data.size]
|
130
|
-
end
|
78
|
+
write_nonblock(data)
|
131
79
|
else
|
132
|
-
|
133
|
-
Timeout.timeout(@data[:write_timeout]) do
|
134
|
-
@socket.write(data)
|
135
|
-
end
|
136
|
-
rescue Timeout::Error
|
137
|
-
raise(Excon::Errors::Timeout.new('write timeout reached'))
|
138
|
-
end
|
80
|
+
write_block(data)
|
139
81
|
end
|
140
82
|
end
|
141
83
|
|
@@ -155,10 +97,10 @@ module Excon
|
|
155
97
|
|
156
98
|
if @data[:proxy]
|
157
99
|
family = @data[:proxy][:family] || ::Socket::Constants::AF_UNSPEC
|
158
|
-
args = [@data[:proxy][:
|
100
|
+
args = [@data[:proxy][:hostname], @data[:proxy][:port], family, ::Socket::Constants::SOCK_STREAM]
|
159
101
|
else
|
160
102
|
family = @data[:family] || ::Socket::Constants::AF_UNSPEC
|
161
|
-
args = [@data[:
|
103
|
+
args = [@data[:hostname], @data[:port], family, ::Socket::Constants::SOCK_STREAM]
|
162
104
|
end
|
163
105
|
if RUBY_VERSION >= '1.9.2' && defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
|
164
106
|
args << nil << nil << false # no reverse lookup
|
@@ -181,27 +123,19 @@ module Excon
|
|
181
123
|
end
|
182
124
|
end
|
183
125
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
else
|
189
|
-
socket.connect(sockaddr)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
rescue Timeout::Error
|
193
|
-
raise Excon::Errors::Timeout.new('connect timeout reached')
|
126
|
+
if @nonblock
|
127
|
+
socket.connect_nonblock(sockaddr)
|
128
|
+
else
|
129
|
+
socket.connect(sockaddr)
|
194
130
|
end
|
195
|
-
|
196
131
|
@socket = socket
|
197
132
|
break
|
198
133
|
rescue Errno::EINPROGRESS
|
199
134
|
unless IO.select(nil, [socket], nil, @data[:connect_timeout])
|
200
|
-
raise(Excon::Errors::Timeout.new(
|
135
|
+
raise(Excon::Errors::Timeout.new('connect timeout reached'))
|
201
136
|
end
|
202
137
|
begin
|
203
138
|
socket.connect_nonblock(sockaddr)
|
204
|
-
|
205
139
|
@socket = socket
|
206
140
|
break
|
207
141
|
rescue Errno::EISCONN
|
@@ -217,10 +151,8 @@ module Excon
|
|
217
151
|
end
|
218
152
|
end
|
219
153
|
|
220
|
-
|
221
|
-
|
222
|
-
raise exception
|
223
|
-
end
|
154
|
+
# this will be our last encountered exception
|
155
|
+
fail exception unless @socket
|
224
156
|
|
225
157
|
if @data[:tcp_nodelay]
|
226
158
|
@socket.setsockopt(::Socket::IPPROTO_TCP,
|
@@ -229,6 +161,136 @@ module Excon
|
|
229
161
|
end
|
230
162
|
end
|
231
163
|
|
164
|
+
def read_nonblock(max_length)
|
165
|
+
begin
|
166
|
+
if max_length
|
167
|
+
until @read_buffer.length >= max_length
|
168
|
+
@read_buffer << @socket.read_nonblock(max_length - @read_buffer.length)
|
169
|
+
end
|
170
|
+
else
|
171
|
+
loop do
|
172
|
+
@read_buffer << @socket.read_nonblock(@data[:chunk_size])
|
173
|
+
end
|
174
|
+
end
|
175
|
+
rescue OpenSSL::SSL::SSLError => error
|
176
|
+
if error.message == 'read would block'
|
177
|
+
if timeout_reached('read')
|
178
|
+
raise_timeout_error('read')
|
179
|
+
else
|
180
|
+
retry
|
181
|
+
end
|
182
|
+
else
|
183
|
+
raise(error)
|
184
|
+
end
|
185
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
186
|
+
if @read_buffer.empty?
|
187
|
+
# if we didn't read anything, try again...
|
188
|
+
if timeout_reached('read')
|
189
|
+
raise_timeout_error('read')
|
190
|
+
else
|
191
|
+
retry
|
192
|
+
end
|
193
|
+
end
|
194
|
+
rescue EOFError
|
195
|
+
@eof = true
|
196
|
+
end
|
197
|
+
|
198
|
+
if max_length
|
199
|
+
if @read_buffer.empty?
|
200
|
+
nil # EOF met at beginning
|
201
|
+
else
|
202
|
+
@read_buffer.slice!(0, max_length)
|
203
|
+
end
|
204
|
+
else
|
205
|
+
# read until EOFError, so return everything
|
206
|
+
@read_buffer.slice!(0, @read_buffer.length)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def read_block(max_length)
|
211
|
+
@socket.read(max_length)
|
212
|
+
rescue OpenSSL::SSL::SSLError => error
|
213
|
+
if error.message == 'read would block'
|
214
|
+
if timeout_reached('read')
|
215
|
+
raise_timeout_error('read')
|
216
|
+
else
|
217
|
+
retry
|
218
|
+
end
|
219
|
+
else
|
220
|
+
raise(error)
|
221
|
+
end
|
222
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
223
|
+
if @read_buffer.empty?
|
224
|
+
if timeout_reached('read')
|
225
|
+
raise_timeout_error('read')
|
226
|
+
else
|
227
|
+
retry
|
228
|
+
end
|
229
|
+
end
|
230
|
+
rescue EOFError
|
231
|
+
@eof = true
|
232
|
+
end
|
233
|
+
|
234
|
+
def write_nonblock(data)
|
235
|
+
if FORCE_ENC
|
236
|
+
data.force_encoding('BINARY')
|
237
|
+
end
|
238
|
+
loop do
|
239
|
+
written = nil
|
240
|
+
begin
|
241
|
+
# I wish that this API accepted a start position, then we wouldn't
|
242
|
+
# have to slice data when there is a short write.
|
243
|
+
written = @socket.write_nonblock(data)
|
244
|
+
rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
|
245
|
+
if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
|
246
|
+
raise error
|
247
|
+
else
|
248
|
+
if timeout_reached('write')
|
249
|
+
raise_timeout_error('write')
|
250
|
+
else
|
251
|
+
retry
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Fast, common case.
|
257
|
+
break if written == data.size
|
258
|
+
|
259
|
+
# This takes advantage of the fact that most ruby implementations
|
260
|
+
# have Copy-On-Write strings. Thusly why requesting a subrange
|
261
|
+
# of data, we actually don't copy data because the new string
|
262
|
+
# simply references a subrange of the original.
|
263
|
+
data = data[written, data.size]
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def write_block(data)
|
268
|
+
@socket.write(data)
|
269
|
+
rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
|
270
|
+
if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
|
271
|
+
raise error
|
272
|
+
else
|
273
|
+
if timeout_reached('write')
|
274
|
+
raise_timeout_error('write')
|
275
|
+
else
|
276
|
+
retry
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def timeout_reached(type)
|
282
|
+
if type == 'read'
|
283
|
+
args = [[@socket], nil, nil, @data[:read_timeout]]
|
284
|
+
else
|
285
|
+
args = [nil, [@socket], nil, @data[:write_timeout]]
|
286
|
+
end
|
287
|
+
IO.select(*args) ? nil : true
|
288
|
+
end
|
289
|
+
|
290
|
+
def raise_timeout_error(type)
|
291
|
+
fail Excon::Errors::Timeout.new("#{type} timeout reached")
|
292
|
+
end
|
293
|
+
|
232
294
|
def unpacked_sockaddr
|
233
295
|
@unpacked_sockaddr ||= ::Socket.unpack_sockaddr_in(@socket.to_io.getsockname)
|
234
296
|
rescue ArgumentError => e
|
@@ -236,6 +298,5 @@ module Excon
|
|
236
298
|
raise
|
237
299
|
end
|
238
300
|
end
|
239
|
-
|
240
301
|
end
|
241
302
|
end
|
data/lib/excon/ssl_socket.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
module Excon
|
2
2
|
class SSLSocket < Socket
|
3
|
-
|
4
|
-
HAVE_NONBLOCK = [:connect_nonblock, :read_nonblock, :write_nonblock].all? {|m|
|
3
|
+
HAVE_NONBLOCK = [:connect_nonblock, :read_nonblock, :write_nonblock].all? do |m|
|
5
4
|
OpenSSL::SSL::SSLSocket.public_method_defined?(m)
|
6
|
-
|
5
|
+
end
|
7
6
|
|
8
7
|
def initialize(data = {})
|
9
8
|
super
|
@@ -16,6 +15,7 @@ module Excon
|
|
16
15
|
if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
|
17
16
|
ssl_context_options &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
|
18
17
|
end
|
18
|
+
|
19
19
|
if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
|
20
20
|
ssl_context_options |= OpenSSL::SSL::OP_NO_COMPRESSION
|
21
21
|
end
|
@@ -25,6 +25,7 @@ module Excon
|
|
25
25
|
if @data[:ssl_version]
|
26
26
|
ssl_context.ssl_version = @data[:ssl_version]
|
27
27
|
end
|
28
|
+
|
28
29
|
if @data[:ssl_verify_peer]
|
29
30
|
# turn verification on
|
30
31
|
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
@@ -46,11 +47,11 @@ module Excon
|
|
46
47
|
|
47
48
|
# workaround issue #257 (JRUBY-6970)
|
48
49
|
ca_file = DEFAULT_CA_FILE
|
49
|
-
ca_file.gsub!(/^jar:/,
|
50
|
+
ca_file.gsub!(/^jar:/, '') if ca_file =~ /^jar:file:\//
|
50
51
|
|
51
52
|
begin
|
52
53
|
ssl_context.cert_store.add_file(ca_file)
|
53
|
-
rescue
|
54
|
+
rescue
|
54
55
|
Excon.display_warning("Excon unable to add file to cert store, ignoring: #{ca_file}\n[#{e.class}] #{e.message}")
|
55
56
|
end
|
56
57
|
end
|
@@ -75,7 +76,7 @@ module Excon
|
|
75
76
|
else
|
76
77
|
ssl_context.key = OpenSSL::PKey::RSA.new(File.read(private_key_path), private_key_pass)
|
77
78
|
end
|
78
|
-
elsif @data.
|
79
|
+
elsif @data.key?(:certificate) && @data.key?(:private_key)
|
79
80
|
ssl_context.cert = OpenSSL::X509::Certificate.new(@data[:certificate])
|
80
81
|
if OpenSSL::PKey.respond_to? :read
|
81
82
|
ssl_context.key = OpenSSL::PKey.read(@data[:private_key], private_key_pass)
|
@@ -90,7 +91,7 @@ module Excon
|
|
90
91
|
|
91
92
|
if @data[:proxy][:password] || @data[:proxy][:user]
|
92
93
|
auth = ['' << @data[:proxy][:user].to_s << ':' << @data[:proxy][:password].to_s].pack('m').delete(Excon::CR_NL)
|
93
|
-
request <<
|
94
|
+
request << 'Proxy-Authorization: Basic ' << auth << Excon::CR_NL
|
94
95
|
end
|
95
96
|
|
96
97
|
request << 'Proxy-Connection: Keep-Alive' << Excon::CR_NL
|
@@ -101,7 +102,7 @@ module Excon
|
|
101
102
|
@socket.write(request)
|
102
103
|
|
103
104
|
# eat the proxy's connection response
|
104
|
-
Excon::Response.parse(self,
|
105
|
+
Excon::Response.parse(self, :expects => 200, :method => 'CONNECT')
|
105
106
|
end
|
106
107
|
|
107
108
|
# convert Socket to OpenSSL::SSL::SSLSocket
|
@@ -114,24 +115,22 @@ module Excon
|
|
114
115
|
end
|
115
116
|
|
116
117
|
begin
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
rescue
|
124
|
-
|
125
|
-
unless error.message == 'read would block'
|
126
|
-
raise error
|
127
|
-
end
|
128
|
-
end
|
118
|
+
if @nonblock
|
119
|
+
loop do
|
120
|
+
begin
|
121
|
+
@socket.connect_nonblock
|
122
|
+
break # connect succeeded
|
123
|
+
rescue OpenSSL::SSL::SSLError => error
|
124
|
+
# would block, rescue and retry as select is non-helpful
|
125
|
+
raise error unless error.message == 'read would block'
|
129
126
|
end
|
130
|
-
else
|
131
|
-
@socket.connect
|
132
127
|
end
|
128
|
+
else
|
129
|
+
@socket.connect
|
133
130
|
end
|
134
|
-
rescue
|
131
|
+
rescue OpenSSL::SSL::SSLError => e
|
132
|
+
raise e
|
133
|
+
rescue
|
135
134
|
raise Excon::Errors::Timeout.new('connect timeout reached')
|
136
135
|
end
|
137
136
|
|
@@ -150,6 +149,5 @@ module Excon
|
|
150
149
|
@nonblock = HAVE_NONBLOCK && @nonblock
|
151
150
|
super
|
152
151
|
end
|
153
|
-
|
154
152
|
end
|
155
153
|
end
|
data/lib/excon/utils.rb
CHANGED
@@ -64,27 +64,21 @@ module Excon
|
|
64
64
|
|
65
65
|
# Escapes HTTP reserved and unwise characters in +str+
|
66
66
|
def escape_uri(str)
|
67
|
-
str = str.dup
|
68
67
|
str.force_encoding('BINARY') if FORCE_ENC
|
69
|
-
str.gsub
|
70
|
-
str
|
68
|
+
str.gsub(UNESCAPED) { "%%%02X" % $1[0].ord }
|
71
69
|
end
|
72
70
|
|
73
71
|
# Unescapes HTTP reserved and unwise characters in +str+
|
74
72
|
def unescape_uri(str)
|
75
|
-
str = str.dup
|
76
73
|
str.force_encoding('BINARY') if FORCE_ENC
|
77
|
-
str.gsub
|
78
|
-
str
|
74
|
+
str.gsub(ESCAPED) { $1.hex.chr }
|
79
75
|
end
|
80
76
|
|
81
77
|
# Unescape form encoded values in +str+
|
82
78
|
def unescape_form(str)
|
83
|
-
str = str.dup
|
84
79
|
str.force_encoding('BINARY') if FORCE_ENC
|
85
80
|
str.gsub!(/\+/, ' ')
|
86
|
-
str.gsub
|
87
|
-
str
|
81
|
+
str.gsub(ESCAPED) { $1.hex.chr }
|
88
82
|
end
|
89
83
|
end
|
90
84
|
end
|
data/tests/basic_tests.rb
CHANGED
@@ -6,6 +6,7 @@ Shindo.tests('Excon basics') do
|
|
6
6
|
tests('GET /content-length/100').returns(200) do
|
7
7
|
connection = Excon::Connection.new({
|
8
8
|
:host => '127.0.0.1',
|
9
|
+
:hostname => '127.0.0.1',
|
9
10
|
:nonblock => false,
|
10
11
|
:port => 9292,
|
11
12
|
:scheme => 'http',
|
@@ -169,6 +170,7 @@ Shindo.tests('Excon basics (ssl file)',['focus']) do
|
|
169
170
|
tests('GET /content-length/100').raises(Excon::Errors::SocketError) do
|
170
171
|
connection = Excon::Connection.new({
|
171
172
|
:host => '127.0.0.1',
|
173
|
+
:hostname => '127.0.0.1',
|
172
174
|
:nonblock => false,
|
173
175
|
:port => 8443,
|
174
176
|
:scheme => 'https',
|
@@ -191,6 +193,7 @@ Shindo.tests('Excon basics (ssl file paths)',['focus']) do
|
|
191
193
|
tests('GET /content-length/100').raises(Excon::Errors::SocketError) do
|
192
194
|
connection = Excon::Connection.new({
|
193
195
|
:host => '127.0.0.1',
|
196
|
+
:hostname => '127.0.0.1',
|
194
197
|
:nonblock => false,
|
195
198
|
:port => 8443,
|
196
199
|
:scheme => 'https',
|
@@ -5,6 +5,7 @@ Shindo.tests('Excon Decompress Middleware') do
|
|
5
5
|
tests('GET /echo%20dirty').returns(200) do
|
6
6
|
connection = Excon::Connection.new({
|
7
7
|
:host => '127.0.0.1',
|
8
|
+
:hostname => '127.0.0.1',
|
8
9
|
:middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::EscapePath],
|
9
10
|
:nonblock => false,
|
10
11
|
:port => 9292,
|
@@ -20,6 +21,7 @@ Shindo.tests('Excon Decompress Middleware') do
|
|
20
21
|
tests('GET /echo dirty').returns(200) do
|
21
22
|
connection = Excon::Connection.new({
|
22
23
|
:host => '127.0.0.1',
|
24
|
+
:hostname => '127.0.0.1',
|
23
25
|
:middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::EscapePath],
|
24
26
|
:nonblock => false,
|
25
27
|
:port => 9292,
|
data/tests/request_tests.rb
CHANGED
@@ -2,55 +2,58 @@ Shindo.tests('Request Tests') do
|
|
2
2
|
with_server('good') do
|
3
3
|
|
4
4
|
tests('persistent connections') do
|
5
|
+
ip_ports = %w(127.0.0.1:9292)
|
6
|
+
ip_ports << "[::1]:9293" unless RUBY_PLATFORM == 'java'
|
7
|
+
ip_ports.each do |ip_port|
|
8
|
+
|
9
|
+
tests("with default :persistent => true, #{ip_port}") do
|
10
|
+
connection = nil
|
11
|
+
|
12
|
+
returns(['1', '2'], 'uses a persistent connection') do
|
13
|
+
connection = Excon.new("http://#{ip_port}", :persistent => true)
|
14
|
+
2.times.map do
|
15
|
+
connection.request(:method => :get, :path => '/echo/request_count').body
|
16
|
+
end
|
17
|
+
end
|
5
18
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
19
|
+
returns(['3', '1', '2'], ':persistent => false resets connection') do
|
20
|
+
ret = []
|
21
|
+
ret << connection.request(:method => :get,
|
22
|
+
:path => '/echo/request_count',
|
23
|
+
:persistent => false).body
|
24
|
+
ret << connection.request(:method => :get,
|
25
|
+
:path => '/echo/request_count').body
|
26
|
+
ret << connection.request(:method => :get,
|
27
|
+
:path => '/echo/request_count').body
|
13
28
|
end
|
14
29
|
end
|
15
30
|
|
16
|
-
|
17
|
-
|
18
|
-
ret << connection.request(:method => :get,
|
19
|
-
:path => '/echo/request_count',
|
20
|
-
:persistent => false).body
|
21
|
-
ret << connection.request(:method => :get,
|
22
|
-
:path => '/echo/request_count').body
|
23
|
-
ret << connection.request(:method => :get,
|
24
|
-
:path => '/echo/request_count').body
|
25
|
-
end
|
26
|
-
end
|
31
|
+
tests("with default :persistent => false, #{ip_port}") do
|
32
|
+
connection = nil
|
27
33
|
|
28
|
-
|
29
|
-
|
34
|
+
returns(['1', '1'], 'does not use a persistent connection') do
|
35
|
+
connection = Excon.new("http://#{ip_port}", :persistent => false)
|
36
|
+
2.times.map do
|
37
|
+
connection.request(:method => :get, :path => '/echo/request_count').body
|
38
|
+
end
|
39
|
+
end
|
30
40
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
41
|
+
returns(['1', '2', '3', '1'], ':persistent => true enables persistence') do
|
42
|
+
ret = []
|
43
|
+
ret << connection.request(:method => :get,
|
44
|
+
:path => '/echo/request_count',
|
45
|
+
:persistent => true).body
|
46
|
+
ret << connection.request(:method => :get,
|
47
|
+
:path => '/echo/request_count',
|
48
|
+
:persistent => true).body
|
49
|
+
ret << connection.request(:method => :get,
|
50
|
+
:path => '/echo/request_count').body
|
51
|
+
ret << connection.request(:method => :get,
|
52
|
+
:path => '/echo/request_count').body
|
35
53
|
end
|
36
54
|
end
|
37
55
|
|
38
|
-
returns(['1', '2', '3', '1'], ':persistent => true enables persistence') do
|
39
|
-
ret = []
|
40
|
-
ret << connection.request(:method => :get,
|
41
|
-
:path => '/echo/request_count',
|
42
|
-
:persistent => true).body
|
43
|
-
ret << connection.request(:method => :get,
|
44
|
-
:path => '/echo/request_count',
|
45
|
-
:persistent => true).body
|
46
|
-
ret << connection.request(:method => :get,
|
47
|
-
:path => '/echo/request_count').body
|
48
|
-
ret << connection.request(:method => :get,
|
49
|
-
:path => '/echo/request_count').body
|
50
|
-
end
|
51
56
|
end
|
52
|
-
|
53
57
|
end
|
54
|
-
|
55
58
|
end
|
56
59
|
end
|
data/tests/servers/good.rb
CHANGED
data/tests/utils_tests.rb
CHANGED
@@ -65,4 +65,17 @@ Shindo.tests('Excon::Utils') do
|
|
65
65
|
end
|
66
66
|
|
67
67
|
end
|
68
|
+
|
69
|
+
tests('#escape_uri').returns('/hello%20excon') do
|
70
|
+
Excon::Utils.escape_uri('/hello excon')
|
71
|
+
end
|
72
|
+
|
73
|
+
tests('#unescape_uri').returns('/hello excon') do
|
74
|
+
Excon::Utils.unescape_uri('/hello%20excon')
|
75
|
+
end
|
76
|
+
|
77
|
+
tests('#unescape_form').returns('message=We love excon!') do
|
78
|
+
Excon::Utils.unescape_form('message=We+love+excon!')
|
79
|
+
end
|
80
|
+
|
68
81
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: excon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.44.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- dpiddy (Dan Peterson)
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-01-
|
13
|
+
date: 2015-01-30 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -46,14 +46,14 @@ dependencies:
|
|
46
46
|
requirements:
|
47
47
|
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
49
|
+
version: 1.0.4
|
50
50
|
type: :development
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version:
|
56
|
+
version: 1.0.4
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
58
|
name: open4
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,6 +124,20 @@ dependencies:
|
|
124
124
|
- - ">="
|
125
125
|
- !ruby/object:Gem::Version
|
126
126
|
version: '0'
|
127
|
+
- !ruby/object:Gem::Dependency
|
128
|
+
name: json
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 1.8.2
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: 1.8.2
|
127
141
|
description: EXtended http(s) CONnections
|
128
142
|
email: geemus@gmail.com
|
129
143
|
executables: []
|
@@ -163,6 +177,7 @@ files:
|
|
163
177
|
- lib/excon/connection.rb
|
164
178
|
- lib/excon/constants.rb
|
165
179
|
- lib/excon/errors.rb
|
180
|
+
- lib/excon/extensions/uri.rb
|
166
181
|
- lib/excon/headers.rb
|
167
182
|
- lib/excon/middlewares/base.rb
|
168
183
|
- lib/excon/middlewares/decompress.rb
|