right_http_connection 1.2.4 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|