http_connection 1.4.0 → 1.4.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.
Files changed (3) hide show
  1. data/lib/right_http_connection.rb +337 -316
  2. metadata +6 -17
  3. data/lib/net_fix.rb +0 -1
@@ -27,7 +27,6 @@ require "time"
27
27
  require "logger"
28
28
 
29
29
 
30
-
31
30
  module RightHttpConnection #:nodoc:
32
31
 
33
32
  end
@@ -67,243 +66,243 @@ the full number of potential reconnects and retries available to
67
66
  them.
68
67
  =end
69
68
 
70
- class HttpConnection
71
-
72
- # Number of times to retry the request after encountering the first error
73
- HTTP_CONNECTION_RETRY_COUNT = 3
74
- # Throw a Timeout::Error if a connection isn't established within this number of seconds
75
- HTTP_CONNECTION_OPEN_TIMEOUT = 5
76
- # Throw a Timeout::Error if no data have been read on this connnection within this number of seconds
77
- HTTP_CONNECTION_READ_TIMEOUT = 120
78
- # Length of the post-error probationary period during which all requests will fail
79
- HTTP_CONNECTION_RETRY_DELAY = 15
80
-
81
- #--------------------
82
- # class methods
83
- #--------------------
84
- #
85
- @@params = {}
86
- @@params[:http_connection_retry_count] = HTTP_CONNECTION_RETRY_COUNT
87
- @@params[:http_connection_open_timeout] = HTTP_CONNECTION_OPEN_TIMEOUT
88
- @@params[:http_connection_read_timeout] = HTTP_CONNECTION_READ_TIMEOUT
89
- @@params[:http_connection_retry_delay] = HTTP_CONNECTION_RETRY_DELAY
90
-
91
- # Query the global (class-level) parameters:
92
- #
93
- # :user_agent => 'www.HostName.com' # String to report as HTTP User agent
94
- # :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.
95
- # :logger => Logger object # If omitted, HttpConnection logs to STDOUT
96
- # :exception => Exception to raise # The type of exception to raise
97
- # # if a request repeatedly fails. RuntimeError is raised if this parameter is omitted.
98
- # :http_connection_retry_count # by default == Rightscale::HttpConnection::HTTP_CONNECTION_RETRY_COUNT
99
- # :http_connection_open_timeout # by default == Rightscale::HttpConnection::HTTP_CONNECTION_OPEN_TIMEOUT
100
- # :http_connection_read_timeout # by default == Rightscale::HttpConnection::HTTP_CONNECTION_READ_TIMEOUT
101
- # :http_connection_retry_delay # by default == Rightscale::HttpConnection::HTTP_CONNECTION_RETRY_DELAY
102
- def self.params
103
- @@params
104
- end
69
+ class HttpConnection
70
+
71
+ # Number of times to retry the request after encountering the first error
72
+ HTTP_CONNECTION_RETRY_COUNT = 3
73
+ # Throw a Timeout::Error if a connection isn't established within this number of seconds
74
+ HTTP_CONNECTION_OPEN_TIMEOUT = 5
75
+ # Throw a Timeout::Error if no data have been read on this connnection within this number of seconds
76
+ HTTP_CONNECTION_READ_TIMEOUT = 120
77
+ # Length of the post-error probationary period during which all requests will fail
78
+ HTTP_CONNECTION_RETRY_DELAY = 15
79
+
80
+ #--------------------
81
+ # class methods
82
+ #--------------------
83
+ #
84
+ @@params = {}
85
+ @@params[:http_connection_retry_count] = HTTP_CONNECTION_RETRY_COUNT
86
+ @@params[:http_connection_open_timeout] = HTTP_CONNECTION_OPEN_TIMEOUT
87
+ @@params[:http_connection_read_timeout] = HTTP_CONNECTION_READ_TIMEOUT
88
+ @@params[:http_connection_retry_delay] = HTTP_CONNECTION_RETRY_DELAY
89
+
90
+ # Query the global (class-level) parameters:
91
+ #
92
+ # :user_agent => 'www.HostName.com' # String to report as HTTP User agent
93
+ # :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.
94
+ # :logger => Logger object # If omitted, HttpConnection logs to STDOUT
95
+ # :exception => Exception to raise # The type of exception to raise
96
+ # # if a request repeatedly fails. RuntimeError is raised if this parameter is omitted.
97
+ # :http_connection_retry_count # by default == Rightscale::HttpConnection::HTTP_CONNECTION_RETRY_COUNT
98
+ # :http_connection_open_timeout # by default == Rightscale::HttpConnection::HTTP_CONNECTION_OPEN_TIMEOUT
99
+ # :http_connection_read_timeout # by default == Rightscale::HttpConnection::HTTP_CONNECTION_READ_TIMEOUT
100
+ # :http_connection_retry_delay # by default == Rightscale::HttpConnection::HTTP_CONNECTION_RETRY_DELAY
101
+ def self.params
102
+ @@params
103
+ end
105
104
 
106
- # Set the global (class-level) parameters
107
- def self.params=(params)
108
- @@params = params
109
- end
105
+ # Set the global (class-level) parameters
106
+ def self.params=(params)
107
+ @@params = params
108
+ end
110
109
 
111
- #------------------
112
- # instance methods
113
- #------------------
114
- attr_accessor :http
115
- attr_accessor :server
116
- attr_accessor :params # see @@params
117
- attr_accessor :logger
118
-
119
- # Params hash:
120
- # :user_agent => 'www.HostName.com' # String to report as HTTP User agent
121
- # :ca_file => 'path_to_file' # A path of a CA certification file in PEM format. The file can contain several CA certificates.
122
- # :logger => Logger object # If omitted, HttpConnection logs to STDOUT
123
- # :exception => Exception to raise # The type of exception to raise if a request repeatedly fails. RuntimeError is raised if this parameter is omitted.
124
- # :http_connection_retry_count # by default == Rightscale::HttpConnection.params[:http_connection_retry_count]
125
- # :http_connection_open_timeout # by default == Rightscale::HttpConnection.params[:http_connection_open_timeout]
126
- # :http_connection_read_timeout # by default == Rightscale::HttpConnection.params[:http_connection_read_timeout]
127
- # :http_connection_retry_delay # by default == Rightscale::HttpConnection.params[:http_connection_retry_delay]
128
- #
129
- def initialize(params={})
130
- @params = params
131
- @params[:http_connection_retry_count] ||= @@params[:http_connection_retry_count]
132
- @params[:http_connection_open_timeout] ||= @@params[:http_connection_open_timeout]
133
- @params[:http_connection_read_timeout] ||= @@params[:http_connection_read_timeout]
134
- @params[:http_connection_retry_delay] ||= @@params[:http_connection_retry_delay]
135
- @http = nil
136
- @server = nil
137
- @logger = get_param(:logger) ||
138
- (RAILS_DEFAULT_LOGGER if defined?(RAILS_DEFAULT_LOGGER)) ||
139
- Logger.new(STDOUT)
140
- end
110
+ #------------------
111
+ # instance methods
112
+ #------------------
113
+ attr_accessor :http
114
+ attr_accessor :server
115
+ attr_accessor :params # see @@params
116
+ attr_accessor :logger
117
+
118
+ # Params hash:
119
+ # :user_agent => 'www.HostName.com' # String to report as HTTP User agent
120
+ # :ca_file => 'path_to_file' # A path of a CA certification file in PEM format. The file can contain several CA certificates.
121
+ # :logger => Logger object # If omitted, HttpConnection logs to STDOUT
122
+ # :exception => Exception to raise # The type of exception to raise if a request repeatedly fails. RuntimeError is raised if this parameter is omitted.
123
+ # :http_connection_retry_count # by default == Rightscale::HttpConnection.params[:http_connection_retry_count]
124
+ # :http_connection_open_timeout # by default == Rightscale::HttpConnection.params[:http_connection_open_timeout]
125
+ # :http_connection_read_timeout # by default == Rightscale::HttpConnection.params[:http_connection_read_timeout]
126
+ # :http_connection_retry_delay # by default == Rightscale::HttpConnection.params[:http_connection_retry_delay]
127
+ #
128
+ def initialize(params={})
129
+ @params = params
130
+ @params[:http_connection_retry_count] ||= @@params[:http_connection_retry_count]
131
+ @params[:http_connection_open_timeout] ||= @@params[:http_connection_open_timeout]
132
+ @params[:http_connection_read_timeout] ||= @@params[:http_connection_read_timeout]
133
+ @params[:http_connection_retry_delay] ||= @@params[:http_connection_retry_delay]
134
+ @http = nil
135
+ @server = nil
136
+ @logger = get_param(:logger) ||
137
+ (RAILS_DEFAULT_LOGGER if defined?(RAILS_DEFAULT_LOGGER)) ||
138
+ Logger.new(STDOUT)
139
+ end
141
140
 
142
- def get_param(name)
143
- @params[name] || @@params[name]
144
- end
141
+ def get_param(name)
142
+ @params[name] || @@params[name]
143
+ end
145
144
 
146
- # Query for the maximum size (in bytes) of a single read from the underlying
147
- # socket. For bulk transfer, especially over fast links, this is value is
148
- # critical to performance.
149
- def socket_read_size?
150
- Net::BufferedIO.socket_read_size?
151
- end
145
+ # Query for the maximum size (in bytes) of a single read from the underlying
146
+ # socket. For bulk transfer, especially over fast links, this is value is
147
+ # critical to performance.
148
+ def socket_read_size?
149
+ Net::BufferedIO.socket_read_size?
150
+ end
152
151
 
153
- # Set the maximum size (in bytes) of a single read from the underlying
154
- # socket. For bulk transfer, especially over fast links, this is value is
155
- # critical to performance.
156
- def socket_read_size=(newsize)
157
- Net::BufferedIO.socket_read_size=(newsize)
158
- end
152
+ # Set the maximum size (in bytes) of a single read from the underlying
153
+ # socket. For bulk transfer, especially over fast links, this is value is
154
+ # critical to performance.
155
+ def socket_read_size=(newsize)
156
+ Net::BufferedIO.socket_read_size=(newsize)
157
+ end
159
158
 
160
- # Query for the maximum size (in bytes) of a single read from local data
161
- # sources like files. This is important, for example, in a streaming PUT of a
162
- # large buffer.
163
- def local_read_size?
164
- Net::HTTPGenericRequest.local_read_size?
165
- end
159
+ # Query for the maximum size (in bytes) of a single read from local data
160
+ # sources like files. This is important, for example, in a streaming PUT of a
161
+ # large buffer.
162
+ def local_read_size?
163
+ Net::HTTPGenericRequest.local_read_size?
164
+ end
166
165
 
167
- # Set the maximum size (in bytes) of a single read from local data
168
- # sources like files. This can be used to tune the performance of, for example, a streaming PUT of a
169
- # large buffer.
170
- def local_read_size=(newsize)
171
- Net::HTTPGenericRequest.local_read_size=(newsize)
172
- end
166
+ # Set the maximum size (in bytes) of a single read from local data
167
+ # sources like files. This can be used to tune the performance of, for example, a streaming PUT of a
168
+ # large buffer.
169
+ def local_read_size=(newsize)
170
+ Net::HTTPGenericRequest.local_read_size=(newsize)
171
+ end
173
172
 
174
- private
175
- #--------------
176
- # Retry state - Keep track of errors on a per-server basis
177
- #--------------
178
- @@state = {} # retry state indexed by server: consecutive error count, error time, and error
179
- @@eof = {}
173
+ private
174
+ #--------------
175
+ # Retry state - Keep track of errors on a per-server basis
176
+ #--------------
177
+ @@state = {} # retry state indexed by server: consecutive error count, error time, and error
178
+ @@eof = {}
180
179
 
181
- # number of consecutive errors seen for server, 0 all is ok
182
- def error_count
183
- @@state[@server] ? @@state[@server][:count] : 0
184
- end
180
+ # number of consecutive errors seen for server, 0 all is ok
181
+ def error_count
182
+ @@state[@server] ? @@state[@server][:count] : 0
183
+ end
185
184
 
186
- # time of last error for server, nil if all is ok
187
- def error_time
188
- @@state[@server] && @@state[@server][:time]
189
- end
185
+ # time of last error for server, nil if all is ok
186
+ def error_time
187
+ @@state[@server] && @@state[@server][:time]
188
+ end
190
189
 
191
- # message for last error for server, "" if all is ok
192
- def error_message
193
- @@state[@server] ? @@state[@server][:message] : ""
194
- end
190
+ # message for last error for server, "" if all is ok
191
+ def error_message
192
+ @@state[@server] ? @@state[@server][:message] : ""
193
+ end
195
194
 
196
- # add an error for a server
197
- def error_add(message)
198
- @@state[@server] = { :count => error_count+1, :time => Time.now, :message => message }
199
- end
195
+ # add an error for a server
196
+ def error_add(message)
197
+ @@state[@server] = {:count => error_count+1, :time => Time.now, :message => message}
198
+ end
200
199
 
201
- # reset the error state for a server (i.e. a request succeeded)
202
- def error_reset
203
- @@state.delete(@server)
204
- end
200
+ # reset the error state for a server (i.e. a request succeeded)
201
+ def error_reset
202
+ @@state.delete(@server)
203
+ end
205
204
 
206
- # Error message stuff...
207
- def banana_message
208
- return "#{@server} temporarily unavailable: (#{error_message})"
209
- end
205
+ # Error message stuff...
206
+ def banana_message
207
+ return "#{@server} temporarily unavailable: (#{error_message})"
208
+ end
210
209
 
211
- def err_header
212
- return "#{self.class.name} :"
213
- end
210
+ def err_header
211
+ return "#{self.class.name} :"
212
+ end
214
213
 
215
- # Adds new EOF timestamp.
216
- # Returns the number of seconds to wait before new conection retry:
217
- # 0.5, 1, 2, 4, 8
218
- def add_eof
219
- (@@eof[@server] ||= []).unshift Time.now
220
- 0.25 * 2 ** @@eof[@server].size
221
- end
214
+ # Adds new EOF timestamp.
215
+ # Returns the number of seconds to wait before new conection retry:
216
+ # 0.5, 1, 2, 4, 8
217
+ def add_eof
218
+ (@@eof[@server] ||= []).unshift Time.now
219
+ 0.25 * 2 ** @@eof[@server].size
220
+ end
222
221
 
223
- # Returns first EOF timestamp or nul if have no EOFs being tracked.
224
- def eof_time
225
- @@eof[@server] && @@eof[@server].last
226
- end
222
+ # Returns first EOF timestamp or nul if have no EOFs being tracked.
223
+ def eof_time
224
+ @@eof[@server] && @@eof[@server].last
225
+ end
227
226
 
228
- # Returns true if we are receiving EOFs during last @params[:http_connection_retry_delay] seconds
229
- # and there were no successful response from server
230
- def raise_on_eof_exception?
231
- @@eof[@server].blank? ? false : ( (Time.now.to_i-@params[:http_connection_retry_delay]) > @@eof[@server].last.to_i )
232
- end
227
+ # Returns true if we are receiving EOFs during last @params[:http_connection_retry_delay] seconds
228
+ # and there were no successful response from server
229
+ def raise_on_eof_exception?
230
+ self.class.blank?(@@eof[@server]) ? false : ((Time.now.to_i-@params[:http_connection_retry_delay]) > @@eof[@server].last.to_i)
231
+ end
233
232
 
234
- # Reset a list of EOFs for this server.
235
- # This is being called when we have got an successful response from server.
236
- def eof_reset
237
- @@eof.delete(@server)
238
- end
233
+ # Reset a list of EOFs for this server.
234
+ # This is being called when we have got an successful response from server.
235
+ def eof_reset
236
+ @@eof.delete(@server)
237
+ end
239
238
 
240
- # Detects if an object is 'streamable' - can we read from it, and can we know the size?
241
- def setup_streaming(request)
242
- if (request.body && request.body.respond_to?(:read))
243
- body = request.body
244
- request.content_length = body.respond_to?(:lstat) ? body.lstat.size : body.size
245
- request.body_stream = request.body
246
- true
247
- end
248
- end
239
+ # Detects if an object is 'streamable' - can we read from it, and can we know the size?
240
+ def setup_streaming(request)
241
+ if (request.body && request.body.respond_to?(:read))
242
+ body = request.body
243
+ request.content_length = body.respond_to?(:lstat) ? body.lstat.size : body.size
244
+ request.body_stream = request.body
245
+ true
246
+ end
247
+ end
249
248
 
250
- def get_fileptr_offset(request_params)
251
- request_params[:request].body.pos
252
- rescue Exception => e
253
- # Probably caught this because the body doesn't support the pos() method, like if it is a socket.
254
- # Just return 0 and get on with life.
255
- 0
256
- end
249
+ def get_fileptr_offset(request_params)
250
+ request_params[:request].body.pos
251
+ rescue Exception => e
252
+ # Probably caught this because the body doesn't support the pos() method, like if it is a socket.
253
+ # Just return 0 and get on with life.
254
+ 0
255
+ end
257
256
 
258
- def reset_fileptr_offset(request, offset = 0)
259
- if (request.body_stream && request.body_stream.respond_to?(:pos))
260
- begin
261
- request.body_stream.pos = offset
262
- rescue Exception => e
263
- @logger.warn("Failed file pointer reset; aborting HTTP retries." +
264
- " -- #{err_header} #{e.inspect}")
265
- raise e
266
- end
267
- end
257
+ def reset_fileptr_offset(request, offset = 0)
258
+ if (request.body_stream && request.body_stream.respond_to?(:pos))
259
+ begin
260
+ request.body_stream.pos = offset
261
+ rescue Exception => e
262
+ @logger.warn("Failed file pointer reset; aborting HTTP retries." +
263
+ " -- #{err_header} #{e.inspect}")
264
+ raise e
268
265
  end
266
+ end
267
+ end
269
268
 
270
- # Start a fresh connection. The object closes any existing connection and
271
- # opens a new one.
272
- def start(request_params)
273
- # close the previous if exists
274
- finish
275
- # create new connection
276
- @server = request_params[:server]
277
- @port = request_params[:port]
278
- @protocol = request_params[:protocol]
279
-
280
- @logger.info("Opening new #{@protocol.upcase} connection to #@server:#@port")
281
- @http = Net::HTTP.new(@server, @port)
282
- @http.open_timeout = @params[:http_connection_open_timeout]
283
- @http.read_timeout = @params[:http_connection_read_timeout]
284
-
285
- if @protocol == 'https'
286
- verifyCallbackProc = Proc.new{ |ok, x509_store_ctx|
287
- code = x509_store_ctx.error
288
- msg = x509_store_ctx.error_string
289
- #debugger
290
- @logger.warn("##### #{@server} certificate verify failed: #{msg}") unless code == 0
291
- true
292
- }
293
- @http.use_ssl = true
294
- @http.verify_mode = OpenSSL::SSL::VERIFY_NONE # Looks like Ruby 1.9 defaults to VERIFY_PEER which doesn't work well
295
- ca_file = get_param(:ca_file)
296
- if ca_file
297
- @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
298
- @http.verify_callback = verifyCallbackProc
299
- @http.ca_file = ca_file
300
- end
301
- end
302
- # open connection
303
- @http.start
269
+ # Start a fresh connection. The object closes any existing connection and
270
+ # opens a new one.
271
+ def start(request_params)
272
+ # close the previous if exists
273
+ finish
274
+ # create new connection
275
+ @server = request_params[:server]
276
+ @port = request_params[:port]
277
+ @protocol = request_params[:protocol]
278
+
279
+ @logger.info("Opening new #{@protocol.upcase} connection to #@server:#@port")
280
+ @http = Net::HTTP.new(@server, @port)
281
+ @http.open_timeout = @params[:http_connection_open_timeout]
282
+ @http.read_timeout = @params[:http_connection_read_timeout]
283
+
284
+ if @protocol == 'https'
285
+ verifyCallbackProc = Proc.new { |ok, x509_store_ctx|
286
+ code = x509_store_ctx.error
287
+ msg = x509_store_ctx.error_string
288
+ #debugger
289
+ @logger.warn("##### #{@server} certificate verify failed: #{msg}") unless code == 0
290
+ true
291
+ }
292
+ @http.use_ssl = true
293
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE # Looks like Ruby 1.9 defaults to VERIFY_PEER which doesn't work well
294
+ ca_file = get_param(:ca_file)
295
+ if ca_file
296
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
297
+ @http.verify_callback = verifyCallbackProc
298
+ @http.ca_file = ca_file
304
299
  end
300
+ end
301
+ # open connection
302
+ @http.start
303
+ end
305
304
 
306
- public
305
+ public
307
306
 
308
307
  =begin rdoc
309
308
  Send HTTP request to server
@@ -317,116 +316,138 @@ them.
317
316
  Raises RuntimeError, Interrupt, and params[:exception] (if specified in new).
318
317
 
319
318
  =end
320
- def request(request_params, &block)
321
- # We save the offset here so that if we need to retry, we can return the file pointer to its initial position
322
- mypos = get_fileptr_offset(request_params)
323
- loop do
324
- # if we are inside a delay between retries: no requests this time!
325
- if error_count > @params[:http_connection_retry_count] &&
326
- error_time + @params[:http_connection_retry_delay] > Time.now
327
- # store the message (otherwise it will be lost after error_reset and
328
- # we will raise an exception with an empty text)
329
- banana_message_text = banana_message
330
- @logger.warn("#{err_header} re-raising same error: #{banana_message_text} " +
331
- "-- error count: #{error_count}, error age: #{Time.now.to_i - error_time.to_i}")
332
- exception = get_param(:exception) || RuntimeError
333
- raise exception.new(banana_message_text)
334
- end
335
-
336
- # try to connect server(if connection does not exist) and get response data
337
- begin
338
- request_params[:protocol] ||= (request_params[:port] == 443 ? 'https' : 'http')
339
-
340
- request = request_params[:request]
341
- request['User-Agent'] = get_param(:user_agent) || ''
342
-
343
- # (re)open connection to server if none exists or params has changed
344
- unless @http &&
345
- @http.started? &&
346
- @server == request_params[:server] &&
347
- @port == request_params[:port] &&
348
- @protocol == request_params[:protocol]
349
- start(request_params)
350
- end
351
-
352
- # Detect if the body is a streamable object like a file or socket. If so, stream that
353
- # bad boy.
354
- setup_streaming(request)
355
- response = @http.request(request, &block)
356
-
357
- error_reset
358
- eof_reset
359
- return response
360
-
361
- # We treat EOF errors and the timeout/network errors differently. Both
362
- # are tracked in different statistics blocks. Note below that EOF
363
- # errors will sleep for a certain (exponentially increasing) period.
364
- # Other errors don't sleep because there is already an inherent delay
365
- # in them; connect and read timeouts (for example) have already
366
- # 'slept'. It is still not clear which way we should treat errors
367
- # like RST and resolution failures. For now, there is no additional
368
- # delay for these errors although this may change in the future.
369
-
370
- # EOFError means the server closed the connection on us.
371
- rescue EOFError => e
372
- @logger.debug("#{err_header} server #{@server} closed connection")
373
- @http = nil
374
-
375
- # if we have waited long enough - raise an exception...
376
- if raise_on_eof_exception?
377
- exception = get_param(:exception) || RuntimeError
378
- @logger.warn("#{err_header} raising #{exception} due to permanent EOF being received from #{@server}, error age: #{Time.now.to_i - eof_time.to_i}")
379
- raise exception.new("Permanent EOF is being received from #{@server}.")
380
- else
381
- # ... else just sleep a bit before new retry
382
- sleep(add_eof)
383
- # We will be retrying the request, so reset the file pointer
384
- reset_fileptr_offset(request, mypos)
385
- end
386
- rescue Exception => e # See comment at bottom for the list of errors seen...
387
- @http = nil
388
- # if ctrl+c is pressed - we have to reraise exception to terminate proggy
389
- if e.is_a?(Interrupt) && !( e.is_a?(Errno::ETIMEDOUT) || e.is_a?(Timeout::Error))
390
- @logger.debug( "#{err_header} request to server #{@server} interrupted by ctrl-c")
391
- raise
392
- elsif e.is_a?(ArgumentError) && e.message.include?('wrong number of arguments (5 for 4)')
393
- # seems our net_fix patch was overriden...
394
- exception = get_param(:exception) || RuntimeError
395
- raise exception.new('incompatible Net::HTTP monkey-patch')
396
- end
397
- # oops - we got a banana: log it
398
- error_add(e.message)
399
- @logger.warn("#{err_header} request failure count: #{error_count}, exception: #{e.inspect}")
400
-
401
- # We will be retrying the request, so reset the file pointer
402
- reset_fileptr_offset(request, mypos)
403
-
404
- end
405
- end
319
+ def request(request_params, &block)
320
+ # We save the offset here so that if we need to retry, we can return the file pointer to its initial position
321
+ mypos = get_fileptr_offset(request_params)
322
+ loop do
323
+ # if we are inside a delay between retries: no requests this time!
324
+ if error_count > @params[:http_connection_retry_count] &&
325
+ error_time + @params[:http_connection_retry_delay] > Time.now
326
+ # store the message (otherwise it will be lost after error_reset and
327
+ # we will raise an exception with an empty text)
328
+ banana_message_text = banana_message
329
+ @logger.warn("#{err_header} re-raising same error: #{banana_message_text} " +
330
+ "-- error count: #{error_count}, error age: #{Time.now.to_i - error_time.to_i}")
331
+ exception = get_param(:exception) || RuntimeError
332
+ raise exception.new(banana_message_text)
406
333
  end
407
334
 
408
- def finish(reason = '')
409
- if @http && @http.started?
410
- reason = ", reason: '#{reason}'" unless reason.blank?
411
- @logger.info("Closing #{@http.use_ssl? ? 'HTTPS' : 'HTTP'} connection to #{@http.address}:#{@http.port}#{reason}")
412
- @http.finish
413
- end
414
- end
335
+ # try to connect server(if connection does not exist) and get response data
336
+ begin
337
+ request_params[:protocol] ||= (request_params[:port] == 443 ? 'https' : 'http')
338
+
339
+ request = request_params[:request]
340
+ request['User-Agent'] = get_param(:user_agent) || ''
341
+
342
+ # (re)open connection to server if none exists or params has changed
343
+ unless @http &&
344
+ @http.started? &&
345
+ @server == request_params[:server] &&
346
+ @port == request_params[:port] &&
347
+ @protocol == request_params[:protocol]
348
+ start(request_params)
349
+ end
350
+
351
+ # Detect if the body is a streamable object like a file or socket. If so, stream that
352
+ # bad boy.
353
+ setup_streaming(request)
354
+ response = @http.request(request, &block)
355
+
356
+ error_reset
357
+ eof_reset
358
+ return response
359
+
360
+ # We treat EOF errors and the timeout/network errors differently. Both
361
+ # are tracked in different statistics blocks. Note below that EOF
362
+ # errors will sleep for a certain (exponentially increasing) period.
363
+ # Other errors don't sleep because there is already an inherent delay
364
+ # in them; connect and read timeouts (for example) have already
365
+ # 'slept'. It is still not clear which way we should treat errors
366
+ # like RST and resolution failures. For now, there is no additional
367
+ # delay for these errors although this may change in the future.
368
+
369
+ # EOFError means the server closed the connection on us.
370
+ rescue EOFError => e
371
+ @logger.debug("#{err_header} server #{@server} closed connection")
372
+ @http = nil
373
+
374
+ # if we have waited long enough - raise an exception...
375
+ if raise_on_eof_exception?
376
+ exception = get_param(:exception) || RuntimeError
377
+ @logger.warn("#{err_header} raising #{exception} due to permanent EOF being received from #{@server}, error age: #{Time.now.to_i - eof_time.to_i}")
378
+ raise exception.new("Permanent EOF is being received from #{@server}.")
379
+ else
380
+ # ... else just sleep a bit before new retry
381
+ sleep(add_eof)
382
+ # We will be retrying the request, so reset the file pointer
383
+ reset_fileptr_offset(request, mypos)
384
+ end
385
+ rescue Exception => e # See comment at bottom for the list of errors seen...
386
+ @http = nil
387
+ # if ctrl+c is pressed - we have to reraise exception to terminate proggy
388
+ if e.is_a?(Interrupt) && !(e.is_a?(Errno::ETIMEDOUT) || e.is_a?(Timeout::Error))
389
+ @logger.debug("#{err_header} request to server #{@server} interrupted by ctrl-c")
390
+ raise
391
+ elsif e.is_a?(ArgumentError) && e.message.include?('wrong number of arguments (5 for 4)')
392
+ # seems our net_fix patch was overriden...
393
+ exception = get_param(:exception) || RuntimeError
394
+ raise exception.new('incompatible Net::HTTP monkey-patch')
395
+ end
396
+ # oops - we got a banana: log it
397
+ error_add(e.message)
398
+ @logger.warn("#{err_header} request failure count: #{error_count}, exception: #{e.inspect}")
399
+
400
+ # We will be retrying the request, so reset the file pointer
401
+ reset_fileptr_offset(request, mypos)
415
402
 
416
- def close(reason='')
417
- finish
418
403
  end
404
+ end
405
+ end
419
406
 
420
- # Errors received during testing:
421
- #
422
- # #<Timeout::Error: execution expired>
423
- # #<Errno::ETIMEDOUT: Connection timed out - connect(2)>
424
- # #<SocketError: getaddrinfo: Name or service not known>
425
- # #<SocketError: getaddrinfo: Temporary failure in name resolution>
426
- # #<EOFError: end of file reached>
427
- # #<Errno::ECONNRESET: Connection reset by peer>
428
- # #<OpenSSL::SSL::SSLError: SSL_write:: bad write retry>
407
+ def finish(reason = '')
408
+ if @http && @http.started?
409
+ reason = ", reason: '#{reason}'" unless self.class.blank?(reason)
410
+ @logger.info("Closing #{@http.use_ssl? ? 'HTTPS' : 'HTTP'} connection to #{@http.address}:#{@http.port}#{reason}")
411
+ @http.finish
412
+ end
429
413
  end
430
414
 
415
+ def close(reason='')
416
+ finish
417
+ end
418
+
419
+ def self.blank?(obj)
420
+ case obj
421
+ when NilClass, FalseClass
422
+ true
423
+ when TrueClass, Numeric
424
+ false
425
+ when Array, Hash
426
+ obj.empty?
427
+ when String
428
+ obj.empty? || obj.strip.empty?
429
+ else
430
+ # "", " ", nil, [], and {} are blank
431
+ if obj.respond_to?(:empty?) && obj.respond_to?(:strip)
432
+ obj.empty? or obj.strip.empty?
433
+ elsif obj.respond_to?(:empty?)
434
+ obj.empty?
435
+ else
436
+ !obj
437
+ end
438
+ end
439
+ end
440
+
441
+ # Errors received during testing:
442
+ #
443
+ # #<Timeout::Error: execution expired>
444
+ # #<Errno::ETIMEDOUT: Connection timed out - connect(2)>
445
+ # #<SocketError: getaddrinfo: Name or service not known>
446
+ # #<SocketError: getaddrinfo: Temporary failure in name resolution>
447
+ # #<EOFError: end of file reached>
448
+ # #<Errno::ECONNRESET: Connection reset by peer>
449
+ # #<OpenSSL::SSL::SSLError: SSL_write:: bad write retry>
450
+ end
451
+
431
452
  end
432
453
 
metadata CHANGED
@@ -1,12 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http_connection
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 1
7
- - 4
8
- - 0
9
- version: 1.4.0
4
+ prerelease:
5
+ version: 1.4.1
10
6
  platform: ruby
11
7
  authors:
12
8
  - Travis Reeder
@@ -15,8 +11,7 @@ autorequire:
15
11
  bindir: bin
16
12
  cert_chain: []
17
13
 
18
- date: 2010-10-17 00:00:00 -07:00
19
- default_executable:
14
+ date: 2011-04-19 00:00:00 Z
20
15
  dependencies: []
21
16
 
22
17
  description: HTTP helper library
@@ -28,16 +23,14 @@ extensions: []
28
23
  extra_rdoc_files:
29
24
  - README.txt
30
25
  files:
31
- - lib/net_fix.rb
32
26
  - lib/right_http_connection.rb
33
27
  - README.txt
34
- has_rdoc: true
35
28
  homepage: http://github.com/appoxy/http_connection/
36
29
  licenses: []
37
30
 
38
31
  post_install_message:
39
- rdoc_options:
40
- - --charset=UTF-8
32
+ rdoc_options: []
33
+
41
34
  require_paths:
42
35
  - lib
43
36
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -45,21 +38,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
38
  requirements:
46
39
  - - ">="
47
40
  - !ruby/object:Gem::Version
48
- segments:
49
- - 0
50
41
  version: "0"
51
42
  required_rubygems_version: !ruby/object:Gem::Requirement
52
43
  none: false
53
44
  requirements:
54
45
  - - ">="
55
46
  - !ruby/object:Gem::Version
56
- segments:
57
- - 0
58
47
  version: "0"
59
48
  requirements: []
60
49
 
61
50
  rubyforge_project:
62
- rubygems_version: 1.3.7
51
+ rubygems_version: 1.7.2
63
52
  signing_key:
64
53
  specification_version: 3
65
54
  summary: HTTP helper library
data/lib/net_fix.rb DELETED
@@ -1 +0,0 @@
1
- # This was out of date and no longer required.