http_connection 1.4.0 → 1.4.1

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