right_http_connection 1.2.3 → 1.2.4
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.
- data/History.txt +8 -1
- data/Rakefile +18 -5
- data/lib/right_http_connection.rb +54 -54
- metadata +6 -6
data/History.txt
CHANGED
@@ -49,4 +49,11 @@ Initial public release
|
|
49
49
|
|
50
50
|
- Added support for setting retry & timeout parameters in the constructor
|
51
51
|
- Improve handling of data streams during upload: if there is a failure and a retry, reset
|
52
|
-
the seek pointer for the subsequent re-request
|
52
|
+
the seek pointer for the subsequent re-request
|
53
|
+
|
54
|
+
== 1.2.4
|
55
|
+
|
56
|
+
* r4984, konstantin, 2008-08-11 14:49:18 +0400
|
57
|
+
* fixed a bug: <NoMethodError: You have a nil object when you didn't expect it!
|
58
|
+
The error occurred while evaluating nil.body_stream>
|
59
|
+
|
data/Rakefile
CHANGED
@@ -12,10 +12,10 @@ include FileUtils
|
|
12
12
|
require File.join(File.dirname(__FILE__), 'lib', 'right_http_connection')
|
13
13
|
|
14
14
|
AUTHOR = 'RightScale' # can also be an array of Authors
|
15
|
-
EMAIL = "
|
15
|
+
EMAIL = "rubygems@rightscale.com"
|
16
16
|
DESCRIPTION = "RightScale's robust HTTP/S connection module"
|
17
17
|
GEM_NAME = 'right_http_connection' # what ppl will type to install your gem
|
18
|
-
RUBYFORGE_PROJECT = '
|
18
|
+
RUBYFORGE_PROJECT = 'rightscale' # The unix name for your project
|
19
19
|
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
20
|
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
21
21
|
|
@@ -29,10 +29,23 @@ RDOC_OPTS = ['--quiet', '--title', 'right_http_connection documentation',
|
|
29
29
|
"--main", "README",
|
30
30
|
"--inline-source"]
|
31
31
|
|
32
|
+
# Suppress Hoe's self-inclusion as a dependency for our Gem. This also keeps
|
33
|
+
# Rake & rubyforge out of the dependency list. Users must manually install
|
34
|
+
# these gems to run tests, etc.
|
35
|
+
# TRB 2/19/09: also do this for the extra_dev_deps array present in newer hoes.
|
36
|
+
# Older versions of RubyGems will try to install developer-dependencies as
|
37
|
+
# required runtime dependencies....
|
32
38
|
class Hoe
|
33
|
-
def extra_deps
|
34
|
-
@extra_deps.reject
|
35
|
-
|
39
|
+
def extra_deps
|
40
|
+
@extra_deps.reject do |x|
|
41
|
+
Array(x).first == 'hoe'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
def extra_dev_deps
|
45
|
+
@extra_dev_deps.reject do |x|
|
46
|
+
Array(x).first == 'hoe'
|
47
|
+
end
|
48
|
+
end
|
36
49
|
end
|
37
50
|
|
38
51
|
# Generate all the Rake tasks
|
@@ -34,7 +34,7 @@ module RightHttpConnection #:nodoc:
|
|
34
34
|
module VERSION #:nodoc:
|
35
35
|
MAJOR = 1
|
36
36
|
MINOR = 2
|
37
|
-
TINY =
|
37
|
+
TINY = 4
|
38
38
|
|
39
39
|
STRING = [MAJOR, MINOR, TINY].join('.')
|
40
40
|
end
|
@@ -42,7 +42,7 @@ end
|
|
42
42
|
|
43
43
|
|
44
44
|
module Rightscale
|
45
|
-
|
45
|
+
|
46
46
|
=begin rdoc
|
47
47
|
HttpConnection maintains a persistent HTTP connection to a remote
|
48
48
|
server. Each instance maintains its own unique connection to the
|
@@ -75,16 +75,16 @@ the full number of potential reconnects and retries available to
|
|
75
75
|
them.
|
76
76
|
=end
|
77
77
|
|
78
|
-
class HttpConnection
|
78
|
+
class HttpConnection
|
79
79
|
|
80
80
|
# Number of times to retry the request after encountering the first error
|
81
|
-
HTTP_CONNECTION_RETRY_COUNT = 3
|
81
|
+
HTTP_CONNECTION_RETRY_COUNT = 3
|
82
82
|
# Throw a Timeout::Error if a connection isn't established within this number of seconds
|
83
|
-
HTTP_CONNECTION_OPEN_TIMEOUT = 5
|
83
|
+
HTTP_CONNECTION_OPEN_TIMEOUT = 5
|
84
84
|
# Throw a Timeout::Error if no data have been read on this connnection within this number of seconds
|
85
|
-
HTTP_CONNECTION_READ_TIMEOUT = 120
|
86
|
-
# Length of the post-error probationary period during which all requests will fail
|
87
|
-
HTTP_CONNECTION_RETRY_DELAY = 15
|
85
|
+
HTTP_CONNECTION_READ_TIMEOUT = 120
|
86
|
+
# Length of the post-error probationary period during which all requests will fail
|
87
|
+
HTTP_CONNECTION_RETRY_DELAY = 15
|
88
88
|
|
89
89
|
#--------------------
|
90
90
|
# class methods
|
@@ -95,10 +95,10 @@ them.
|
|
95
95
|
@@params[:http_connection_open_timeout] = HTTP_CONNECTION_OPEN_TIMEOUT
|
96
96
|
@@params[:http_connection_read_timeout] = HTTP_CONNECTION_READ_TIMEOUT
|
97
97
|
@@params[:http_connection_retry_delay] = HTTP_CONNECTION_RETRY_DELAY
|
98
|
-
|
98
|
+
|
99
99
|
# Query the global (class-level) parameters:
|
100
|
-
#
|
101
|
-
# :user_agent => 'www.HostName.com' # String to report as HTTP User agent
|
100
|
+
#
|
101
|
+
# :user_agent => 'www.HostName.com' # String to report as HTTP User agent
|
102
102
|
# :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.
|
103
103
|
# :logger => Logger object # If omitted, HttpConnection logs to STDOUT
|
104
104
|
# :exception => Exception to raise # The type of exception to raise
|
@@ -110,7 +110,7 @@ them.
|
|
110
110
|
def self.params
|
111
111
|
@@params
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
# Set the global (class-level) parameters
|
115
115
|
def self.params=(params)
|
116
116
|
@@params = params
|
@@ -125,7 +125,7 @@ them.
|
|
125
125
|
attr_accessor :logger
|
126
126
|
|
127
127
|
# Params hash:
|
128
|
-
# :user_agent => 'www.HostName.com' # String to report as HTTP User agent
|
128
|
+
# :user_agent => 'www.HostName.com' # String to report as HTTP User agent
|
129
129
|
# :ca_file => 'path_to_file' # A path of a CA certification file in PEM format. The file can contain several CA certificates.
|
130
130
|
# :logger => Logger object # If omitted, HttpConnection logs to STDOUT
|
131
131
|
# :exception => Exception to raise # The type of exception to raise if a request repeatedly fails. RuntimeError is raised if this parameter is omitted.
|
@@ -142,7 +142,7 @@ them.
|
|
142
142
|
@params[:http_connection_retry_delay] ||= @@params[:http_connection_retry_delay]
|
143
143
|
@http = nil
|
144
144
|
@server = nil
|
145
|
-
@logger = get_param(:logger) ||
|
145
|
+
@logger = get_param(:logger) ||
|
146
146
|
(RAILS_DEFAULT_LOGGER if defined?(RAILS_DEFAULT_LOGGER)) ||
|
147
147
|
Logger.new(STDOUT)
|
148
148
|
end
|
@@ -164,7 +164,7 @@ them.
|
|
164
164
|
def socket_read_size=(newsize)
|
165
165
|
Net::BufferedIO.socket_read_size=(newsize)
|
166
166
|
end
|
167
|
-
|
167
|
+
|
168
168
|
# Query for the maximum size (in bytes) of a single read from local data
|
169
169
|
# sources like files. This is important, for example, in a streaming PUT of a
|
170
170
|
# large buffer.
|
@@ -190,27 +190,27 @@ them.
|
|
190
190
|
def error_count
|
191
191
|
@@state[@server] ? @@state[@server][:count] : 0
|
192
192
|
end
|
193
|
-
|
193
|
+
|
194
194
|
# time of last error for server, nil if all is ok
|
195
195
|
def error_time
|
196
196
|
@@state[@server] && @@state[@server][:time]
|
197
197
|
end
|
198
|
-
|
198
|
+
|
199
199
|
# message for last error for server, "" if all is ok
|
200
200
|
def error_message
|
201
201
|
@@state[@server] ? @@state[@server][:message] : ""
|
202
202
|
end
|
203
|
-
|
203
|
+
|
204
204
|
# add an error for a server
|
205
205
|
def error_add(message)
|
206
206
|
@@state[@server] = { :count => error_count+1, :time => Time.now, :message => message }
|
207
207
|
end
|
208
|
-
|
208
|
+
|
209
209
|
# reset the error state for a server (i.e. a request succeeded)
|
210
210
|
def error_reset
|
211
211
|
@@state.delete(@server)
|
212
212
|
end
|
213
|
-
|
213
|
+
|
214
214
|
# Error message stuff...
|
215
215
|
def banana_message
|
216
216
|
return "#{@server} temporarily unavailable: (#{error_message})"
|
@@ -219,7 +219,7 @@ them.
|
|
219
219
|
def err_header
|
220
220
|
return "#{self.class.name} :"
|
221
221
|
end
|
222
|
-
|
222
|
+
|
223
223
|
# Adds new EOF timestamp.
|
224
224
|
# Returns the number of seconds to wait before new conection retry:
|
225
225
|
# 0.5, 1, 2, 4, 8
|
@@ -232,29 +232,29 @@ them.
|
|
232
232
|
def eof_time
|
233
233
|
@@eof[@server] && @@eof[@server].last
|
234
234
|
end
|
235
|
-
|
235
|
+
|
236
236
|
# Returns true if we are receiving EOFs during last @params[:http_connection_retry_delay] seconds
|
237
237
|
# and there were no successful response from server
|
238
238
|
def raise_on_eof_exception?
|
239
239
|
@@eof[@server].blank? ? false : ( (Time.now.to_i-@params[:http_connection_retry_delay]) > @@eof[@server].last.to_i )
|
240
|
-
end
|
241
|
-
|
240
|
+
end
|
241
|
+
|
242
242
|
# Reset a list of EOFs for this server.
|
243
243
|
# This is being called when we have got an successful response from server.
|
244
244
|
def eof_reset
|
245
245
|
@@eof.delete(@server)
|
246
246
|
end
|
247
|
-
|
248
|
-
# Detects if an object is 'streamable' - can we read from it, and can we know the size?
|
247
|
+
|
248
|
+
# Detects if an object is 'streamable' - can we read from it, and can we know the size?
|
249
249
|
def setup_streaming(request)
|
250
250
|
if(request.body && request.body.respond_to?(:read))
|
251
251
|
body = request.body
|
252
|
-
request.content_length = body.respond_to?(:lstat) ? body.lstat.size : body.size
|
252
|
+
request.content_length = body.respond_to?(:lstat) ? body.lstat.size : body.size
|
253
253
|
request.body_stream = request.body
|
254
254
|
true
|
255
255
|
end
|
256
256
|
end
|
257
|
-
|
257
|
+
|
258
258
|
def get_fileptr_offset(request_params)
|
259
259
|
request_params[:request].body.pos
|
260
260
|
rescue Exception => e
|
@@ -262,7 +262,7 @@ them.
|
|
262
262
|
# Just return 0 and get on with life.
|
263
263
|
0
|
264
264
|
end
|
265
|
-
|
265
|
+
|
266
266
|
def reset_fileptr_offset(request, offset = 0)
|
267
267
|
if(request.body_stream && request.body_stream.respond_to?(:pos))
|
268
268
|
begin
|
@@ -272,7 +272,7 @@ them.
|
|
272
272
|
" -- #{err_header} #{e.inspect}")
|
273
273
|
raise e
|
274
274
|
end
|
275
|
-
end
|
275
|
+
end
|
276
276
|
end
|
277
277
|
|
278
278
|
# Start a fresh connection. The object closes any existing connection and
|
@@ -284,12 +284,12 @@ them.
|
|
284
284
|
@server = request_params[:server]
|
285
285
|
@port = request_params[:port]
|
286
286
|
@protocol = request_params[:protocol]
|
287
|
-
|
287
|
+
|
288
288
|
@logger.info("Opening new #{@protocol.upcase} connection to #@server:#@port")
|
289
289
|
@http = Net::HTTP.new(@server, @port)
|
290
290
|
@http.open_timeout = @params[:http_connection_open_timeout]
|
291
291
|
@http.read_timeout = @params[:http_connection_read_timeout]
|
292
|
-
|
292
|
+
|
293
293
|
if @protocol == 'https'
|
294
294
|
verifyCallbackProc = Proc.new{ |ok, x509_store_ctx|
|
295
295
|
code = x509_store_ctx.error
|
@@ -303,7 +303,7 @@ them.
|
|
303
303
|
if ca_file
|
304
304
|
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
305
305
|
@http.verify_callback = verifyCallbackProc
|
306
|
-
@http.ca_file = ca_file
|
306
|
+
@http.ca_file = ca_file
|
307
307
|
end
|
308
308
|
end
|
309
309
|
# open connection
|
@@ -312,55 +312,55 @@ them.
|
|
312
312
|
|
313
313
|
public
|
314
314
|
|
315
|
-
=begin rdoc
|
315
|
+
=begin rdoc
|
316
316
|
Send HTTP request to server
|
317
317
|
|
318
318
|
request_params hash:
|
319
319
|
:server => 'www.HostName.com' # Hostname or IP address of HTTP server
|
320
|
-
:port => '80' # Port of HTTP server
|
321
|
-
:protocol => 'https' # http and https are supported on any port
|
320
|
+
:port => '80' # Port of HTTP server
|
321
|
+
:protocol => 'https' # http and https are supported on any port
|
322
322
|
:request => 'requeststring' # Fully-formed HTTP request to make
|
323
323
|
|
324
324
|
Raises RuntimeError, Interrupt, and params[:exception] (if specified in new).
|
325
|
-
|
325
|
+
|
326
326
|
=end
|
327
327
|
def request(request_params, &block)
|
328
328
|
# We save the offset here so that if we need to retry, we can return the file pointer to its initial position
|
329
329
|
mypos = get_fileptr_offset(request_params)
|
330
330
|
loop do
|
331
331
|
# if we are inside a delay between retries: no requests this time!
|
332
|
-
if error_count > @params[:http_connection_retry_count] &&
|
332
|
+
if error_count > @params[:http_connection_retry_count] &&
|
333
333
|
error_time + @params[:http_connection_retry_delay] > Time.now
|
334
334
|
# store the message (otherwise it will be lost after error_reset and
|
335
335
|
# we will raise an exception with an empty text)
|
336
336
|
banana_message_text = banana_message
|
337
337
|
@logger.warn("#{err_header} re-raising same error: #{banana_message_text} " +
|
338
|
-
"-- error count: #{error_count}, error age: #{Time.now.to_i - error_time.to_i}")
|
338
|
+
"-- error count: #{error_count}, error age: #{Time.now.to_i - error_time.to_i}")
|
339
339
|
exception = get_param(:exception) || RuntimeError
|
340
340
|
raise exception.new(banana_message_text)
|
341
341
|
end
|
342
|
-
|
342
|
+
|
343
343
|
# try to connect server(if connection does not exist) and get response data
|
344
344
|
begin
|
345
345
|
request_params[:protocol] ||= (request_params[:port] == 443 ? 'https' : 'http')
|
346
|
+
|
347
|
+
request = request_params[:request]
|
348
|
+
request['User-Agent'] = get_param(:user_agent) || ''
|
349
|
+
|
346
350
|
# (re)open connection to server if none exists or params has changed
|
347
|
-
unless @http &&
|
351
|
+
unless @http &&
|
348
352
|
@http.started? &&
|
349
353
|
@server == request_params[:server] &&
|
350
354
|
@port == request_params[:port] &&
|
351
355
|
@protocol == request_params[:protocol]
|
352
356
|
start(request_params)
|
353
357
|
end
|
354
|
-
|
355
|
-
# get response and return it
|
356
|
-
request = request_params[:request]
|
357
|
-
request['User-Agent'] = get_param(:user_agent) || ''
|
358
358
|
|
359
359
|
# Detect if the body is a streamable object like a file or socket. If so, stream that
|
360
360
|
# bad boy.
|
361
361
|
setup_streaming(request)
|
362
362
|
response = @http.request(request, &block)
|
363
|
-
|
363
|
+
|
364
364
|
error_reset
|
365
365
|
eof_reset
|
366
366
|
return response
|
@@ -373,26 +373,26 @@ them.
|
|
373
373
|
# 'slept'. It is still not clear which way we should treat errors
|
374
374
|
# like RST and resolution failures. For now, there is no additional
|
375
375
|
# delay for these errors although this may change in the future.
|
376
|
-
|
376
|
+
|
377
377
|
# EOFError means the server closed the connection on us.
|
378
378
|
rescue EOFError => e
|
379
379
|
@logger.debug("#{err_header} server #{@server} closed connection")
|
380
380
|
@http = nil
|
381
|
-
|
381
|
+
|
382
382
|
# if we have waited long enough - raise an exception...
|
383
383
|
if raise_on_eof_exception?
|
384
384
|
exception = get_param(:exception) || RuntimeError
|
385
|
-
@logger.warn("#{err_header} raising #{exception} due to permanent EOF being received from #{@server}, error age: #{Time.now.to_i - eof_time.to_i}")
|
385
|
+
@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
386
|
raise exception.new("Permanent EOF is being received from #{@server}.")
|
387
387
|
else
|
388
388
|
# ... else just sleep a bit before new retry
|
389
389
|
sleep(add_eof)
|
390
390
|
# We will be retrying the request, so reset the file pointer
|
391
391
|
reset_fileptr_offset(request, mypos)
|
392
|
-
end
|
392
|
+
end
|
393
393
|
rescue Exception => e # See comment at bottom for the list of errors seen...
|
394
394
|
@http = nil
|
395
|
-
# if ctrl+c is pressed - we have to reraise exception to terminate proggy
|
395
|
+
# if ctrl+c is pressed - we have to reraise exception to terminate proggy
|
396
396
|
if e.is_a?(Interrupt) && !( e.is_a?(Errno::ETIMEDOUT) || e.is_a?(Timeout::Error))
|
397
397
|
@logger.debug( "#{err_header} request to server #{@server} interrupted by ctrl-c")
|
398
398
|
raise
|
@@ -407,7 +407,7 @@ them.
|
|
407
407
|
|
408
408
|
# We will be retrying the request, so reset the file pointer
|
409
409
|
reset_fileptr_offset(request, mypos)
|
410
|
-
|
410
|
+
|
411
411
|
end
|
412
412
|
end
|
413
413
|
end
|
@@ -416,7 +416,7 @@ them.
|
|
416
416
|
if @http && @http.started?
|
417
417
|
reason = ", reason: '#{reason}'" unless reason.blank?
|
418
418
|
@logger.info("Closing #{@http.use_ssl? ? 'HTTPS' : 'HTTP'} connection to #{@http.address}:#{@http.port}#{reason}")
|
419
|
-
@http.finish
|
419
|
+
@http.finish
|
420
420
|
end
|
421
421
|
end
|
422
422
|
|
@@ -430,6 +430,6 @@ them.
|
|
430
430
|
# #<Errno::ECONNRESET: Connection reset by peer>
|
431
431
|
# #<OpenSSL::SSL::SSLError: SSL_write:: bad write retry>
|
432
432
|
end
|
433
|
-
|
433
|
+
|
434
434
|
end
|
435
435
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: right_http_connection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- RightScale
|
@@ -9,12 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-02-24 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
16
|
description: RightScale's robust HTTP/S connection module
|
17
|
-
email:
|
17
|
+
email: rubygems@rightscale.com
|
18
18
|
executables: []
|
19
19
|
|
20
20
|
extensions: []
|
@@ -32,7 +32,7 @@ files:
|
|
32
32
|
- lib/right_http_connection.rb
|
33
33
|
- setup.rb
|
34
34
|
has_rdoc: true
|
35
|
-
homepage: http://
|
35
|
+
homepage: http://rightscale.rubyforge.org
|
36
36
|
post_install_message:
|
37
37
|
rdoc_options:
|
38
38
|
- --main
|
@@ -53,8 +53,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
53
|
version:
|
54
54
|
requirements: []
|
55
55
|
|
56
|
-
rubyforge_project:
|
57
|
-
rubygems_version: 1.
|
56
|
+
rubyforge_project: rightscale
|
57
|
+
rubygems_version: 1.3.1
|
58
58
|
signing_key:
|
59
59
|
specification_version: 2
|
60
60
|
summary: RightScale's robust HTTP/S connection module
|