right_http_connection 1.2.4 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/History.txt +27 -0
- data/Manifest.txt +1 -0
- data/Rakefile +46 -57
- data/lib/{net_fix.rb → base/net_fix.rb} +24 -4
- data/lib/base/support.rb +109 -0
- data/lib/base/version.rb +32 -0
- data/lib/right_http_connection.rb +198 -80
- data/right_http_connection.gemspec +63 -0
- data/spec/bad.ca +2794 -0
- data/spec/ca/Rakefile +64 -0
- data/spec/ca/ca.crt +23 -0
- data/spec/ca/ca.key +18 -0
- data/spec/ca/demoCA/index.txt +1 -0
- data/spec/ca/demoCA/serial +1 -0
- data/spec/ca/passphrase.txt +1 -0
- data/spec/ca/server.csr +12 -0
- data/spec/client/cacert.cer +0 -0
- data/spec/client/cacert.pem +17 -0
- data/spec/client/cert.pem +18 -0
- data/spec/client/key.pem +27 -0
- data/spec/good.ca +23 -0
- data/spec/proxy_server.rb +75 -0
- data/spec/really_dumb_webserver.rb +122 -0
- data/spec/server.crt +62 -0
- data/spec/server.key +15 -0
- metadata +130 -36
- data/setup.rb +0 -1585
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2007-
|
2
|
+
# Copyright (c) 2007-2011 RightScale Inc
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -27,19 +27,9 @@ require "time"
|
|
27
27
|
require "logger"
|
28
28
|
|
29
29
|
$:.unshift(File.dirname(__FILE__))
|
30
|
-
require
|
31
|
-
|
32
|
-
|
33
|
-
module RightHttpConnection #:nodoc:
|
34
|
-
module VERSION #:nodoc:
|
35
|
-
MAJOR = 1
|
36
|
-
MINOR = 2
|
37
|
-
TINY = 4
|
38
|
-
|
39
|
-
STRING = [MAJOR, MINOR, TINY].join('.')
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
30
|
+
require 'base/version'
|
31
|
+
require 'base/support'
|
32
|
+
require 'base/net_fix'
|
43
33
|
|
44
34
|
module Rightscale
|
45
35
|
|
@@ -78,13 +68,13 @@ them.
|
|
78
68
|
class HttpConnection
|
79
69
|
|
80
70
|
# Number of times to retry the request after encountering the first error
|
81
|
-
HTTP_CONNECTION_RETRY_COUNT = 3
|
71
|
+
HTTP_CONNECTION_RETRY_COUNT = 3 unless defined?(HTTP_CONNECTION_RETRY_COUNT)
|
82
72
|
# Throw a Timeout::Error if a connection isn't established within this number of seconds
|
83
|
-
HTTP_CONNECTION_OPEN_TIMEOUT = 5
|
73
|
+
HTTP_CONNECTION_OPEN_TIMEOUT = 5 unless defined?(HTTP_CONNECTION_OPEN_TIMEOUT)
|
84
74
|
# Throw a Timeout::Error if no data have been read on this connnection within this number of seconds
|
85
|
-
HTTP_CONNECTION_READ_TIMEOUT = 120
|
75
|
+
HTTP_CONNECTION_READ_TIMEOUT = 120 unless defined?(HTTP_CONNECTION_READ_TIMEOUT)
|
86
76
|
# Length of the post-error probationary period during which all requests will fail
|
87
|
-
HTTP_CONNECTION_RETRY_DELAY = 15
|
77
|
+
HTTP_CONNECTION_RETRY_DELAY = 15 unless defined?(HTTP_CONNECTION_RETRY_DELAY)
|
88
78
|
|
89
79
|
#--------------------
|
90
80
|
# class methods
|
@@ -100,13 +90,19 @@ them.
|
|
100
90
|
#
|
101
91
|
# :user_agent => 'www.HostName.com' # String to report as HTTP User agent
|
102
92
|
# :ca_file => 'path_to_file' # Path to a CA certification file in PEM format. The file can contain several CA certificates. If this parameter isn't set, HTTPS certs won't be verified.
|
93
|
+
# :fail_if_ca_mismatch => Boolean # If ca_file is set and the server certificate doesn't verify, a log line is generated regardless, but normally right_http_connection continues on past the failure. If this is set, fail to connect in that case. Defaults to false.
|
103
94
|
# :logger => Logger object # If omitted, HttpConnection logs to STDOUT
|
104
95
|
# :exception => Exception to raise # The type of exception to raise
|
105
96
|
# # if a request repeatedly fails. RuntimeError is raised if this parameter is omitted.
|
97
|
+
# :proxy_host => 'hostname' # hostname of HTTP proxy host to use, default none.
|
98
|
+
# :proxy_port => port # port of HTTP proxy host to use, default none.
|
99
|
+
# :proxy_username => 'username' # username to use for proxy authentication, default none.
|
100
|
+
# :proxy_password => 'password' # password to use for proxy authentication, default none.
|
106
101
|
# :http_connection_retry_count # by default == Rightscale::HttpConnection::HTTP_CONNECTION_RETRY_COUNT
|
107
102
|
# :http_connection_open_timeout # by default == Rightscale::HttpConnection::HTTP_CONNECTION_OPEN_TIMEOUT
|
108
103
|
# :http_connection_read_timeout # by default == Rightscale::HttpConnection::HTTP_CONNECTION_READ_TIMEOUT
|
109
104
|
# :http_connection_retry_delay # by default == Rightscale::HttpConnection::HTTP_CONNECTION_RETRY_DELAY
|
105
|
+
# :raise_on_timeout # do not perform a retry if timeout is received (false by default)
|
110
106
|
def self.params
|
111
107
|
@@params
|
112
108
|
end
|
@@ -127,28 +123,63 @@ them.
|
|
127
123
|
# Params hash:
|
128
124
|
# :user_agent => 'www.HostName.com' # String to report as HTTP User agent
|
129
125
|
# :ca_file => 'path_to_file' # A path of a CA certification file in PEM format. The file can contain several CA certificates.
|
126
|
+
# :fail_if_ca_mismatch => Boolean # If ca_file is set and the server certificate doesn't verify, a log line is generated regardless, but normally right_http_connection continues on past the failure. If this is set, fail to connect in that case. Defaults to false.
|
130
127
|
# :logger => Logger object # If omitted, HttpConnection logs to STDOUT
|
131
128
|
# :exception => Exception to raise # The type of exception to raise if a request repeatedly fails. RuntimeError is raised if this parameter is omitted.
|
129
|
+
# :proxy_host => 'hostname' # hostname of HTTP proxy host to use, default none.
|
130
|
+
# :proxy_port => port # port of HTTP proxy host to use, default none.
|
131
|
+
# :proxy_username => 'username' # username to use for proxy authentication, default none.
|
132
|
+
# :proxy_password => 'password' # password to use for proxy authentication, default none.
|
132
133
|
# :http_connection_retry_count # by default == Rightscale::HttpConnection.params[:http_connection_retry_count]
|
133
134
|
# :http_connection_open_timeout # by default == Rightscale::HttpConnection.params[:http_connection_open_timeout]
|
134
135
|
# :http_connection_read_timeout # by default == Rightscale::HttpConnection.params[:http_connection_read_timeout]
|
135
136
|
# :http_connection_retry_delay # by default == Rightscale::HttpConnection.params[:http_connection_retry_delay]
|
136
|
-
#
|
137
|
+
# :raise_on_timeout # do not perform a retry if timeout is received (false by default)
|
137
138
|
def initialize(params={})
|
138
139
|
@params = params
|
140
|
+
|
141
|
+
#set up logging first
|
142
|
+
@logger = get_param(:logger) ||
|
143
|
+
(RAILS_DEFAULT_LOGGER if defined?(RAILS_DEFAULT_LOGGER)) ||
|
144
|
+
Logger.new(STDOUT)
|
145
|
+
|
146
|
+
env_proxy_host, env_proxy_port, env_proxy_username, env_proxy_password = get_proxy_info_for_env if ENV['HTTP_PROXY']
|
147
|
+
|
139
148
|
@params[:http_connection_retry_count] ||= @@params[:http_connection_retry_count]
|
140
149
|
@params[:http_connection_open_timeout] ||= @@params[:http_connection_open_timeout]
|
141
150
|
@params[:http_connection_read_timeout] ||= @@params[:http_connection_read_timeout]
|
142
151
|
@params[:http_connection_retry_delay] ||= @@params[:http_connection_retry_delay]
|
152
|
+
@params[:proxy_host] ||= @@params[:proxy_host] || env_proxy_host
|
153
|
+
@params[:proxy_port] ||= @@params[:proxy_port] || env_proxy_port
|
154
|
+
@params[:proxy_username] ||= @@params[:proxy_username] || env_proxy_username
|
155
|
+
@params[:proxy_password] ||= @@params[:proxy_password] || env_proxy_password
|
156
|
+
|
143
157
|
@http = nil
|
144
158
|
@server = nil
|
145
|
-
|
146
|
-
|
147
|
-
|
159
|
+
#--------------
|
160
|
+
# Retry state - Keep track of errors on a per-server basis
|
161
|
+
#--------------
|
162
|
+
@state = {} # retry state indexed by server: consecutive error count, error time, and error
|
163
|
+
|
164
|
+
@eof = {}
|
148
165
|
end
|
149
166
|
|
150
|
-
def
|
151
|
-
|
167
|
+
def get_proxy_info_for_env
|
168
|
+
parsed_uri = URI.parse(ENV['HTTP_PROXY'])
|
169
|
+
if parsed_uri.scheme.to_s.downcase == 'http'
|
170
|
+
return parsed_uri.host, parsed_uri.port, parsed_uri.user, parsed_uri.password
|
171
|
+
else
|
172
|
+
@logger.warn "Invalid protocol in ENV['HTTP_PROXY'] URI = #{ENV['HTTP_PROXY'].inspect} expecting 'http' got #{parsed_uri.scheme.inspect}"
|
173
|
+
return
|
174
|
+
end
|
175
|
+
rescue Exception => e
|
176
|
+
@logger.warn "Error parsing ENV['HTTP_PROXY'] with exception: #{e.message}"
|
177
|
+
return
|
178
|
+
end
|
179
|
+
private :get_proxy_info_for_env
|
180
|
+
|
181
|
+
def get_param(name, custom_options={})
|
182
|
+
custom_options [name] || @params[name] || @@params[name]
|
152
183
|
end
|
153
184
|
|
154
185
|
# Query for the maximum size (in bytes) of a single read from the underlying
|
@@ -180,40 +211,37 @@ them.
|
|
180
211
|
end
|
181
212
|
|
182
213
|
private
|
183
|
-
#--------------
|
184
|
-
# Retry state - Keep track of errors on a per-server basis
|
185
|
-
#--------------
|
186
|
-
@@state = {} # retry state indexed by server: consecutive error count, error time, and error
|
187
|
-
@@eof = {}
|
188
214
|
|
189
215
|
# number of consecutive errors seen for server, 0 all is ok
|
190
216
|
def error_count
|
191
|
-
|
217
|
+
@state[@server] ? @state[@server][:count] : 0
|
192
218
|
end
|
193
219
|
|
194
220
|
# time of last error for server, nil if all is ok
|
195
221
|
def error_time
|
196
|
-
|
222
|
+
@state[@server] && @state[@server][:time]
|
197
223
|
end
|
198
224
|
|
199
225
|
# message for last error for server, "" if all is ok
|
200
226
|
def error_message
|
201
|
-
|
227
|
+
@state[@server] ? @state[@server][:message] : ""
|
202
228
|
end
|
203
229
|
|
204
230
|
# add an error for a server
|
205
|
-
def error_add(
|
206
|
-
|
231
|
+
def error_add(error)
|
232
|
+
message = error
|
233
|
+
message = "#{error.class.name}: #{error.message}" if error.is_a?(Exception)
|
234
|
+
@state[@server] = { :count => error_count+1, :time => Time.now, :message => message }
|
207
235
|
end
|
208
236
|
|
209
237
|
# reset the error state for a server (i.e. a request succeeded)
|
210
238
|
def error_reset
|
211
|
-
|
239
|
+
@state.delete(@server)
|
212
240
|
end
|
213
241
|
|
214
242
|
# Error message stuff...
|
215
243
|
def banana_message
|
216
|
-
return "#{@server} temporarily unavailable: (#{error_message})"
|
244
|
+
return "#{@protocol}://#{@server}:#{@port} temporarily unavailable: (#{error_message})"
|
217
245
|
end
|
218
246
|
|
219
247
|
def err_header
|
@@ -224,25 +252,25 @@ them.
|
|
224
252
|
# Returns the number of seconds to wait before new conection retry:
|
225
253
|
# 0.5, 1, 2, 4, 8
|
226
254
|
def add_eof
|
227
|
-
(
|
228
|
-
0.25 * 2 **
|
255
|
+
(@eof[@server] ||= []).unshift Time.now
|
256
|
+
0.25 * 2 ** @eof[@server].size
|
229
257
|
end
|
230
258
|
|
231
259
|
# Returns first EOF timestamp or nul if have no EOFs being tracked.
|
232
260
|
def eof_time
|
233
|
-
|
261
|
+
@eof[@server] && @eof[@server].last
|
234
262
|
end
|
235
263
|
|
236
264
|
# Returns true if we are receiving EOFs during last @params[:http_connection_retry_delay] seconds
|
237
265
|
# and there were no successful response from server
|
238
266
|
def raise_on_eof_exception?
|
239
|
-
|
267
|
+
@eof[@server].nil? ? false : ( (Time.now.to_i-@params[:http_connection_retry_delay]) > @eof[@server].last.to_i )
|
240
268
|
end
|
241
269
|
|
242
270
|
# Reset a list of EOFs for this server.
|
243
271
|
# This is being called when we have got an successful response from server.
|
244
272
|
def eof_reset
|
245
|
-
|
273
|
+
@eof.delete(@server)
|
246
274
|
end
|
247
275
|
|
248
276
|
# Detects if an object is 'streamable' - can we read from it, and can we know the size?
|
@@ -275,35 +303,85 @@ them.
|
|
275
303
|
end
|
276
304
|
end
|
277
305
|
|
306
|
+
SECURITY_PARAMS = [:cert, :key, :cert_file, :key_file, :ca_file]
|
307
|
+
|
278
308
|
# Start a fresh connection. The object closes any existing connection and
|
279
309
|
# opens a new one.
|
280
310
|
def start(request_params)
|
281
311
|
# close the previous if exists
|
282
312
|
finish
|
283
313
|
# create new connection
|
284
|
-
@server
|
285
|
-
@port
|
286
|
-
@protocol
|
314
|
+
@server = request_params[:server]
|
315
|
+
@port = request_params[:port]
|
316
|
+
@protocol = request_params[:protocol]
|
317
|
+
@proxy_host = request_params[:proxy_host]
|
318
|
+
@proxy_port = request_params[:proxy_port]
|
319
|
+
@proxy_username = request_params[:proxy_username]
|
320
|
+
@proxy_password = request_params[:proxy_password]
|
321
|
+
|
322
|
+
SECURITY_PARAMS.each do |param_name|
|
323
|
+
@params[param_name] = request_params[param_name]
|
324
|
+
end
|
287
325
|
|
288
326
|
@logger.info("Opening new #{@protocol.upcase} connection to #@server:#@port")
|
289
|
-
|
290
|
-
@
|
291
|
-
|
327
|
+
|
328
|
+
@logger.info("Connecting to proxy #{@proxy_host}:#{@proxy_port} with username" +
|
329
|
+
" #{@proxy_username.inspect}") unless @proxy_host.nil?
|
330
|
+
|
331
|
+
@http = Net::HTTP.new(@server, @port, @proxy_host, @proxy_port, @proxy_username,
|
332
|
+
@proxy_password)
|
333
|
+
@http.open_timeout = get_param(:http_connection_open_timeout, request_params)
|
334
|
+
@http.read_timeout = get_param(:http_connection_read_timeout, request_params)
|
292
335
|
|
293
336
|
if @protocol == 'https'
|
294
337
|
verifyCallbackProc = Proc.new{ |ok, x509_store_ctx|
|
338
|
+
# List of error codes: http://www.openssl.org/docs/apps/verify.html
|
295
339
|
code = x509_store_ctx.error
|
296
340
|
msg = x509_store_ctx.error_string
|
297
|
-
|
298
|
-
|
299
|
-
|
341
|
+
if request_params[:fail_if_ca_mismatch] && code != 0
|
342
|
+
false
|
343
|
+
else
|
344
|
+
true
|
345
|
+
end
|
300
346
|
}
|
301
347
|
@http.use_ssl = true
|
348
|
+
|
302
349
|
ca_file = get_param(:ca_file)
|
303
|
-
if ca_file
|
304
|
-
|
350
|
+
if ca_file && File.exists?(ca_file)
|
351
|
+
# Documentation for 'http.rb':
|
352
|
+
# : verify_mode, verify_mode=((|mode|))
|
353
|
+
# Sets the flags for server the certification verification at
|
354
|
+
# beginning of SSL/TLS session.
|
355
|
+
# OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable.
|
356
|
+
#
|
357
|
+
# KHRVI: looks like the constant VERIFY_FAIL_IF_NO_PEER_CERT is not acceptable
|
305
358
|
@http.verify_callback = verifyCallbackProc
|
306
|
-
@http.ca_file
|
359
|
+
@http.ca_file= ca_file
|
360
|
+
@http.verify_mode = get_param(:use_server_auth) ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
361
|
+
# The depth count is 'level 0:peer certificate', 'level 1: CA certificate', 'level 2: higher level CA certificate', and so on.
|
362
|
+
# Setting the maximum depth to 2 allows the levels 0, 1, and 2. The default depth limit is 9, allowing for the peer certificate and additional 9 CA certificates.
|
363
|
+
@http.verify_depth = 9
|
364
|
+
else
|
365
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
366
|
+
end
|
367
|
+
|
368
|
+
# CERT
|
369
|
+
cert_file = get_param(:cert_file, request_params)
|
370
|
+
cert = File.read(cert_file) if cert_file && File.exists?(cert_file)
|
371
|
+
cert ||= get_param(:cert, request_params)
|
372
|
+
# KEY
|
373
|
+
key_file = get_param(:key_file, request_params)
|
374
|
+
key = File.read(key_file) if key_file && File.exists?(key_file)
|
375
|
+
key ||= get_param(:key, request_params)
|
376
|
+
if cert && key
|
377
|
+
begin
|
378
|
+
@http.verify_callback = verifyCallbackProc
|
379
|
+
@http.cert = OpenSSL::X509::Certificate.new(cert)
|
380
|
+
@http.key = OpenSSL::PKey::RSA.new(key)
|
381
|
+
rescue OpenSSL::PKey::RSAError, OpenSSL::X509::CertificateError => e
|
382
|
+
@logger.error "##### Error loading SSL client cert or key: #{e.message} :: backtrace #{e.backtrace}"
|
383
|
+
raise e
|
384
|
+
end
|
307
385
|
end
|
308
386
|
end
|
309
387
|
# open connection
|
@@ -320,45 +398,72 @@ them.
|
|
320
398
|
:port => '80' # Port of HTTP server
|
321
399
|
:protocol => 'https' # http and https are supported on any port
|
322
400
|
:request => 'requeststring' # Fully-formed HTTP request to make
|
401
|
+
:proxy_host => 'hostname' # hostname of HTTP proxy host to use, default none.
|
402
|
+
:proxy_port => port # port of HTTP proxy host to use, default none.
|
403
|
+
:proxy_username => 'username' # username to use for proxy authentication, default none.
|
404
|
+
:proxy_password => 'password' # password to use for proxy authentication, default none.
|
405
|
+
|
406
|
+
:raise_on_timeout # do not perform a retry if timeout is received (false by default)
|
407
|
+
:http_connection_retry_count
|
408
|
+
:http_connection_open_timeout
|
409
|
+
:http_connection_read_timeout
|
410
|
+
:http_connection_retry_delay
|
411
|
+
:user_agent
|
412
|
+
:exception
|
323
413
|
|
324
414
|
Raises RuntimeError, Interrupt, and params[:exception] (if specified in new).
|
325
415
|
|
326
416
|
=end
|
327
417
|
def request(request_params, &block)
|
418
|
+
current_params = @params.merge(request_params)
|
419
|
+
exception = get_param(:exception, current_params) || RuntimeError
|
420
|
+
|
421
|
+
# Re-establish the connection if any of auth params has changed
|
422
|
+
same_auth_params_as_before = SECURITY_PARAMS.select do |param|
|
423
|
+
request_params[param] != get_param(param)
|
424
|
+
end.empty?
|
425
|
+
|
328
426
|
# We save the offset here so that if we need to retry, we can return the file pointer to its initial position
|
329
|
-
mypos = get_fileptr_offset(
|
427
|
+
mypos = get_fileptr_offset(current_params)
|
330
428
|
loop do
|
429
|
+
|
430
|
+
current_params[:protocol] ||= (current_params[:port] == 443 ? 'https' : 'http')
|
431
|
+
# (re)open connection to server if none exists or params has changed
|
432
|
+
same_server_as_before = @server == current_params[:server] &&
|
433
|
+
@port == current_params[:port] &&
|
434
|
+
@protocol == current_params[:protocol] &&
|
435
|
+
same_auth_params_as_before
|
436
|
+
|
331
437
|
# if we are inside a delay between retries: no requests this time!
|
332
|
-
if
|
333
|
-
|
438
|
+
# (skip this step if the endpoint has changed)
|
439
|
+
if error_count > current_params[:http_connection_retry_count] &&
|
440
|
+
error_time + current_params[:http_connection_retry_delay] > Time.now &&
|
441
|
+
same_server_as_before
|
442
|
+
|
334
443
|
# store the message (otherwise it will be lost after error_reset and
|
335
444
|
# we will raise an exception with an empty text)
|
336
445
|
banana_message_text = banana_message
|
337
446
|
@logger.warn("#{err_header} re-raising same error: #{banana_message_text} " +
|
338
447
|
"-- error count: #{error_count}, error age: #{Time.now.to_i - error_time.to_i}")
|
339
|
-
exception = get_param(:exception) || RuntimeError
|
340
448
|
raise exception.new(banana_message_text)
|
341
449
|
end
|
342
450
|
|
343
451
|
# try to connect server(if connection does not exist) and get response data
|
344
452
|
begin
|
345
|
-
|
346
|
-
|
347
|
-
request = request_params[:request]
|
348
|
-
request['User-Agent'] = get_param(:user_agent) || ''
|
349
|
-
|
350
|
-
# (re)open connection to server if none exists or params has changed
|
453
|
+
request = current_params[:request]
|
454
|
+
request['User-Agent'] = get_param(:user_agent, current_params) || ''
|
351
455
|
unless @http &&
|
352
456
|
@http.started? &&
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
start(request_params)
|
457
|
+
same_server_as_before
|
458
|
+
same_auth_params_as_before = true
|
459
|
+
start(current_params)
|
357
460
|
end
|
358
461
|
|
359
462
|
# Detect if the body is a streamable object like a file or socket. If so, stream that
|
360
463
|
# bad boy.
|
361
464
|
setup_streaming(request)
|
465
|
+
# update READ_TIMEOUT value (it can be passed with request_params hash)
|
466
|
+
@http.read_timeout = get_param(:http_connection_read_timeout, current_params)
|
362
467
|
response = @http.request(request, &block)
|
363
468
|
|
364
469
|
error_reset
|
@@ -376,12 +481,12 @@ them.
|
|
376
481
|
|
377
482
|
# EOFError means the server closed the connection on us.
|
378
483
|
rescue EOFError => e
|
484
|
+
finish(e.message)
|
485
|
+
|
379
486
|
@logger.debug("#{err_header} server #{@server} closed connection")
|
380
|
-
@http = nil
|
381
487
|
|
382
488
|
# if we have waited long enough - raise an exception...
|
383
489
|
if raise_on_eof_exception?
|
384
|
-
exception = get_param(:exception) || RuntimeError
|
385
490
|
@logger.warn("#{err_header} raising #{exception} due to permanent EOF being received from #{@server}, error age: #{Time.now.to_i - eof_time.to_i}")
|
386
491
|
raise exception.new("Permanent EOF is being received from #{@server}.")
|
387
492
|
else
|
@@ -390,34 +495,47 @@ them.
|
|
390
495
|
# We will be retrying the request, so reset the file pointer
|
391
496
|
reset_fileptr_offset(request, mypos)
|
392
497
|
end
|
393
|
-
rescue
|
394
|
-
|
395
|
-
|
396
|
-
if e.
|
397
|
-
@logger.debug( "#{err_header} request to server #{@server} interrupted by ctrl-c")
|
398
|
-
raise
|
399
|
-
elsif e.is_a?(ArgumentError) && e.message.include?('wrong number of arguments (5 for 4)')
|
498
|
+
rescue ArgumentError => e
|
499
|
+
finish(e.message)
|
500
|
+
|
501
|
+
if e.message.include?('wrong number of arguments (5 for 4)')
|
400
502
|
# seems our net_fix patch was overriden...
|
401
|
-
exception = get_param(:exception) || RuntimeError
|
402
503
|
raise exception.new('incompatible Net::HTTP monkey-patch')
|
504
|
+
else
|
505
|
+
raise e
|
506
|
+
end
|
507
|
+
|
508
|
+
rescue Timeout::Error, SocketError, SystemCallError, Interrupt => e # See comment at bottom for the list of errors seen...
|
509
|
+
finish(e.message)
|
510
|
+
if e.is_a?(Errno::ETIMEDOUT) || e.is_a?(Timeout::Error)
|
511
|
+
# Omit retries if it was explicitly requested
|
512
|
+
# #6481:
|
513
|
+
# ... When creating a resource in EC2 (instance, volume, snapshot, etc) it is undetermined what happened if the call times out.
|
514
|
+
# The resource may or may not have been created in EC2. Retrying the call may cause multiple resources to be created...
|
515
|
+
raise exception.new("#{e.class.name}: #{e.message}") if current_params[:raise_on_timeout]
|
516
|
+
elsif e.is_a?(Interrupt)
|
517
|
+
# if ctrl+c is pressed - we have to reraise exception to terminate proggy
|
518
|
+
@logger.debug( "#{err_header} request to server #{@server} interrupted by ctrl-c")
|
519
|
+
raise e
|
403
520
|
end
|
404
521
|
# oops - we got a banana: log it
|
405
|
-
error_add(e
|
522
|
+
error_add(e)
|
406
523
|
@logger.warn("#{err_header} request failure count: #{error_count}, exception: #{e.inspect}")
|
407
524
|
|
408
525
|
# We will be retrying the request, so reset the file pointer
|
409
526
|
reset_fileptr_offset(request, mypos)
|
410
|
-
|
411
527
|
end
|
412
528
|
end
|
413
529
|
end
|
414
530
|
|
415
531
|
def finish(reason = '')
|
416
532
|
if @http && @http.started?
|
417
|
-
reason = ", reason: '#{reason}'" unless reason.
|
533
|
+
reason = ", reason: '#{reason}'" unless reason.empty?
|
418
534
|
@logger.info("Closing #{@http.use_ssl? ? 'HTTPS' : 'HTTP'} connection to #{@http.address}:#{@http.port}#{reason}")
|
419
535
|
@http.finish
|
420
536
|
end
|
537
|
+
ensure
|
538
|
+
@http = nil
|
421
539
|
end
|
422
540
|
|
423
541
|
# Errors received during testing:
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#-- -*- mode: ruby; encoding: utf-8 -*-
|
2
|
+
# Copyright: Copyright (c) 2010 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'rubygems'
|
25
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'base', 'version'))
|
26
|
+
|
27
|
+
Gem::Specification.new do |spec|
|
28
|
+
spec.name = 'right_http_connection'
|
29
|
+
spec.rubyforge_project = 'rightscale'
|
30
|
+
spec.version = RightHttpConnection::VERSION::STRING
|
31
|
+
spec.authors = ['RightScale, Inc.']
|
32
|
+
spec.email = 'rubygems@rightscale.com'
|
33
|
+
spec.homepage = 'http://rightscale.rubyforge.org/'
|
34
|
+
spec.summary = 'RightScale\'s robust HTTP/S connection module'
|
35
|
+
spec.has_rdoc = true
|
36
|
+
spec.rdoc_options = ['--quiet', '--main', 'README.txt', '--title',
|
37
|
+
'right_http_connection documentation', '--opname',
|
38
|
+
'index.html', '--line-numbers', '--inline-source']
|
39
|
+
spec.extra_rdoc_files = ['README.txt']
|
40
|
+
spec.required_ruby_version = '>= 1.8.7'
|
41
|
+
spec.require_path = 'lib'
|
42
|
+
|
43
|
+
spec.add_development_dependency('rake', '< 12')
|
44
|
+
spec.add_development_dependency('rspec', "~> 2.3")
|
45
|
+
spec.add_development_dependency('cucumber', "~> 0.8")
|
46
|
+
spec.add_development_dependency('flexmock', "~> 0.8.11")
|
47
|
+
spec.add_development_dependency('trollop', "~> 1.16")
|
48
|
+
|
49
|
+
spec.description = <<-EOF
|
50
|
+
Rightscale::HttpConnection is a robust HTTP/S library. It implements a retry
|
51
|
+
algorithm for low-level network errors.
|
52
|
+
|
53
|
+
== FEATURES:
|
54
|
+
|
55
|
+
- provides put/get streaming
|
56
|
+
- does configurable retries on connect and read timeouts, DNS failures, etc.
|
57
|
+
- HTTPS certificate checking
|
58
|
+
EOF
|
59
|
+
|
60
|
+
candidates = Dir.glob('{lib,spec}/**/*') + ['History.txt', 'Manifest.txt', 'README.txt', 'Rakefile',
|
61
|
+
'right_http_connection.gemspec']
|
62
|
+
spec.files = candidates.sort
|
63
|
+
end
|