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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 77a0da513895a3cba30b16145e7bea66033973cb
4
- data.tar.gz: c573a10ec3c2f569628b1c605dc6d47a25caecf5
3
+ metadata.gz: 90759d86297bebbce2b0374cb730466df92321ec
4
+ data.tar.gz: 67e4b85deb2f2b52455b190fa4c3df120fb1c532
5
5
  SHA512:
6
- metadata.gz: 740736aa1390923d0ce8d30b4739b149509cdfe445e5ea90ef21e2f1f69e1df2603d22d722b9d4ac835c349e503d03349fbfcdc978104057edcc1310d130c654
7
- data.tar.gz: fa08c954a4fcb5bc746d3ab32194b7bb41fef491b86b8ac3d5e0436d6c816f3074232e4ee807b132fc450d53f2f84ffe3a770738e5130c7ec65818faa5feea76
6
+ metadata.gz: bda8264156cb7f3983aaac5cd8327254cc0f06a1ec9f1dc701271798cf06bfe2c207e14fb55bdd9b6deb96ce15843c0b2c6b0f7a5fbb66ead49a21d199b9f976
7
+ data.tar.gz: 511d4b47dd504535f78a25c4a741fcaea3206ec466fe57c199c6ae8c4ee8d5111ca5a1f8a39cb3e80cfb6f25d7f226df74a2b748f4b2392e3ece823479e470e8
data/Gemfile CHANGED
@@ -5,7 +5,7 @@ gemspec
5
5
  gem 'jruby-openssl', :platform => :jruby
6
6
  gem 'unicorn', :platforms => [:mri, :rbx]
7
7
  gem 'rubysl', '~> 2.0', :platform => :rbx
8
- gem 'rack', '~> 1.5'
8
+ gem 'rack', '~> 1.6'
9
9
 
10
10
  # group :benchmark do
11
11
  # gem 'em-http-request'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- excon (0.43.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.0)
17
- eventmachine (1.0.0-java)
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.7.3)
24
- json (1.7.3-java)
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.5.2)
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.5)
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-2014 [CONTRIBUTORS.md](https://github.com/excon/excon/blob/master/CONTRIBUTORS.md)
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
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
3
  require 'date'
4
-
4
+ include Rake::DSL
5
5
  #############################################################################
6
6
  #
7
7
  # Helper functions
@@ -1,3 +1,13 @@
1
+ 0.44.0 01/30/2015
2
+ =================
3
+
4
+ re-implement timeout using IO.select
5
+ document custom URI parser usage
6
+ fix ruby 2.2 build
7
+ improved IPv6 support
8
+ Excon::Utils improvements
9
+ add 429 errors
10
+
1
11
  0.43.0 01/09/2015
2
12
  =================
3
13
 
@@ -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.43.0'
17
- s.date = '2015-01-09'
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
@@ -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
- # Respect Ruby's $VERBOSE setting, unless EXCON_DEBUG is set
54
- if !$VERBOSE.nil? || ENV['EXCON_DEBUG']
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] || Excon.defaults[: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!(params)
119
+ }.merge(params)
117
120
  if uri.password
118
121
  params[:password] = Utils.unescape_uri(uri.password)
119
122
  end
@@ -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,
@@ -1,6 +1,6 @@
1
1
  module Excon
2
2
 
3
- VERSION = '0.43.0'
3
+ VERSION = '0.44.0'
4
4
 
5
5
  CR_NL = "\r\n"
6
6
 
@@ -73,6 +73,7 @@ module Excon
73
73
  :connect_timeout,
74
74
  :family,
75
75
  :host,
76
+ :hostname,
76
77
  :omit_default_port,
77
78
  :nonblock,
78
79
  :reuseaddr,
@@ -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
@@ -26,6 +26,7 @@ module Excon
26
26
  params.merge!(
27
27
  :scheme => uri.scheme || datum[:scheme],
28
28
  :host => uri.host || datum[:host],
29
+ :hostname => uri.hostname || datum[:hostname],
29
30
  :port => uri.port || datum[:port],
30
31
  :path => uri.path,
31
32
  :query => uri.query
@@ -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
- begin
36
- if max_length
37
- until @read_buffer.length >= max_length
38
- @read_buffer << @socket.read_nonblock(max_length - @read_buffer.length)
39
- end
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
- if max_length
69
- if @read_buffer.empty?
70
- nil # EOF met at beginning
71
- else
72
- @read_buffer.slice!(0, max_length)
73
- end
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
- # read until EOFError, so return everything
76
- @read_buffer.slice!(0, @read_buffer.length)
51
+ retry
77
52
  end
78
- else
79
- begin
80
- Timeout.timeout(@data[:read_timeout]) do
81
- @socket.read(max_length)
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
- rescue Timeout::Error
84
- raise Excon::Errors::Timeout.new('read timeout reached')
60
+ else
61
+ raise(error)
85
62
  end
86
63
  end
87
64
  end
88
65
 
89
- def readline
90
- begin
66
+ def legacy_readline
67
+ begin
91
68
  Timeout.timeout(@data[:read_timeout]) do
92
69
  @socket.readline
93
- end
70
+ end
94
71
  rescue Timeout::Error
95
- raise Excon::Errors::Timeout.new('read timeout reached')
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
- if FORCE_ENC
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
- begin
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][:host], @data[:proxy][:port], family, ::Socket::Constants::SOCK_STREAM]
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[:host], @data[:port], family, ::Socket::Constants::SOCK_STREAM]
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
- begin
185
- Timeout.timeout(@data[:connect_timeout]) do
186
- if @nonblock
187
- socket.connect_nonblock(sockaddr)
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("connect timeout reached"))
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
- unless @socket
221
- # this will be our last encountered exception
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
@@ -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:/, "") if ca_file =~ /^jar:file:\//
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 => e
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.has_key?(:certificate) && @data.has_key?(:private_key)
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 << "Proxy-Authorization: Basic " << auth << Excon::CR_NL
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, { :expects => 200, :method => "CONNECT" })
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
- Timeout.timeout(@data[:connect_timeout]) do
118
- if @nonblock
119
- while true
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
- 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 Timeout::Error
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
@@ -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!(UNESCAPED) { "%%%02X" % $1[0].ord }
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!(ESCAPED) { $1.hex.chr }
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!(ESCAPED) { $1.hex.chr }
87
- str
81
+ str.gsub(ESCAPED) { $1.hex.chr }
88
82
  end
89
83
  end
90
84
  end
@@ -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,
@@ -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
- tests('with default :persistent => true') do
7
- connection = nil
8
-
9
- returns(['1', '2'], 'uses a persistent connection') do
10
- connection = Excon.new('http://127.0.0.1:9292', :persistent => true)
11
- 2.times.map do
12
- connection.request(:method => :get, :path => '/echo/request_count').body
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
- returns(['3', '1', '2'], ':persistent => false resets connection') do
17
- ret = []
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
- tests('with default :persistent => false') do
29
- connection = nil
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
- returns(['1', '1'], 'does not use a persistent connection') do
32
- connection = Excon.new('http://127.0.0.1:9292', :persistent => false)
33
- 2.times.map do
34
- connection.request(:method => :get, :path => '/echo/request_count').body
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
@@ -333,5 +333,6 @@ end
333
333
 
334
334
  EM.run do
335
335
  EM.start_server("127.0.0.1", 9292, GoodServer)
336
+ EM.start_server("::1", 9293, GoodServer) unless RUBY_PLATFORM == 'java'
336
337
  $stderr.puts "ready"
337
338
  end
@@ -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.43.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-09 00:00:00.000000000 Z
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: '0'
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: '0'
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