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.
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2007-2008 RightScale Inc
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 "net_fix"
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
- @logger = get_param(:logger) ||
146
- (RAILS_DEFAULT_LOGGER if defined?(RAILS_DEFAULT_LOGGER)) ||
147
- Logger.new(STDOUT)
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 get_param(name)
151
- @params[name] || @@params[name]
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
- @@state[@server] ? @@state[@server][:count] : 0
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
- @@state[@server] && @@state[@server][:time]
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
- @@state[@server] ? @@state[@server][:message] : ""
227
+ @state[@server] ? @state[@server][:message] : ""
202
228
  end
203
229
 
204
230
  # add an error for a server
205
- def error_add(message)
206
- @@state[@server] = { :count => error_count+1, :time => Time.now, :message => message }
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
- @@state.delete(@server)
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
- (@@eof[@server] ||= []).unshift Time.now
228
- 0.25 * 2 ** @@eof[@server].size
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
- @@eof[@server] && @@eof[@server].last
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
- @@eof[@server].blank? ? false : ( (Time.now.to_i-@params[:http_connection_retry_delay]) > @@eof[@server].last.to_i )
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
- @@eof.delete(@server)
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 = request_params[:server]
285
- @port = request_params[:port]
286
- @protocol = request_params[: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
- @http = Net::HTTP.new(@server, @port)
290
- @http.open_timeout = @params[:http_connection_open_timeout]
291
- @http.read_timeout = @params[:http_connection_read_timeout]
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
- #debugger
298
- @logger.warn("##### #{@server} certificate verify failed: #{msg}") unless code == 0
299
- true
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
- @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
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 = 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(request_params)
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 error_count > @params[:http_connection_retry_count] &&
333
- error_time + @params[:http_connection_retry_delay] > Time.now
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
- request_params[:protocol] ||= (request_params[:port] == 443 ? 'https' : 'http')
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
- @server == request_params[:server] &&
354
- @port == request_params[:port] &&
355
- @protocol == request_params[:protocol]
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 Exception => e # See comment at bottom for the list of errors seen...
394
- @http = nil
395
- # if ctrl+c is pressed - we have to reraise exception to terminate proggy
396
- if e.is_a?(Interrupt) && !( e.is_a?(Errno::ETIMEDOUT) || e.is_a?(Timeout::Error))
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.message)
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.blank?
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