right_http_connection 1.2.3 → 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.txt +35 -1
- data/Manifest.txt +1 -0
- data/Rakefile +46 -44
- data/lib/{net_fix.rb → base/net_fix.rb} +24 -4
- data/lib/base/support.rb +109 -0
- data/lib/base/version.rb +32 -0
- data/lib/right_http_connection.rb +237 -119
- data/right_http_connection.gemspec +63 -0
- data/spec/bad.ca +2794 -0
- data/spec/ca/Rakefile +64 -0
- data/spec/ca/ca.crt +23 -0
- data/spec/ca/ca.key +18 -0
- data/spec/ca/demoCA/index.txt +1 -0
- data/spec/ca/demoCA/serial +1 -0
- data/spec/ca/passphrase.txt +1 -0
- data/spec/ca/server.csr +12 -0
- data/spec/client/cacert.cer +0 -0
- data/spec/client/cacert.pem +17 -0
- data/spec/client/cert.pem +18 -0
- data/spec/client/key.pem +27 -0
- data/spec/good.ca +23 -0
- data/spec/proxy_server.rb +75 -0
- data/spec/really_dumb_webserver.rb +122 -0
- data/spec/server.crt +62 -0
- data/spec/server.key +15 -0
- metadata +132 -38
- data/setup.rb +0 -1585
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8ba2dac9f5194cfcb8a8e6dec24f973418d07f22
|
4
|
+
data.tar.gz: 59e6d3f82e45ca7679849320fc7fd89f6e776d20
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fd754b030589555f947f2a52bb64c4d3524c902f3e0d95c00fbecf5a340c06d7d6353360763b225b3e05464357610e2dae54d7e21df17b899a65c3bef5430c02
|
7
|
+
data.tar.gz: 2ee74c4fc7cf86908f8a06b96be6cd892aeb25e16a345ea959e0fbe7907712a126c73704c8a3da013dec9443cecd8cc6e1caace3ebd4737c7e0e4da4e5287488
|
data/History.txt
CHANGED
@@ -49,4 +49,38 @@ 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
|
+
|
60
|
+
== 1.2.5
|
61
|
+
|
62
|
+
- ActiveSupport dependency removal
|
63
|
+
|
64
|
+
|
65
|
+
== 1.3.0
|
66
|
+
- Added:
|
67
|
+
- support for using through proxies
|
68
|
+
- functional tests
|
69
|
+
|
70
|
+
== 1.3.1
|
71
|
+
- Added:
|
72
|
+
- SSL certificate handshake support
|
73
|
+
- more specs
|
74
|
+
- ability to give client side key and certificate inline
|
75
|
+
- Fixed: some minor glitches
|
76
|
+
|
77
|
+
== 1.4.0
|
78
|
+
- Added
|
79
|
+
- license
|
80
|
+
- HTTP_PROXY env variable support
|
81
|
+
- Fixed
|
82
|
+
- context-length issue (thx kristianm)
|
83
|
+
- exception handling
|
84
|
+
- connection is now closed on any exception
|
85
|
+
- connection is reestablished when credentials change
|
86
|
+
- some other minor bugs
|
data/Manifest.txt
CHANGED
data/Rakefile
CHANGED
@@ -1,59 +1,34 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler::GemHelper.install_tasks
|
2
4
|
require 'rake'
|
3
5
|
require 'rake/clean'
|
4
6
|
require 'rake/testtask'
|
5
7
|
require 'rake/packagetask'
|
6
|
-
require 'rake/gempackagetask'
|
7
|
-
require 'rake/rdoctask'
|
8
|
-
require 'rake/contrib/rubyforgepublisher'
|
8
|
+
#require 'rake/gempackagetask'
|
9
|
+
#require 'rake/rdoctask'
|
10
|
+
#require 'rake/contrib/rubyforgepublisher'
|
11
|
+
require 'rspec/core/rake_task'
|
12
|
+
require 'cucumber/rake/task'
|
9
13
|
require 'fileutils'
|
10
|
-
require 'hoe'
|
11
14
|
include FileUtils
|
12
15
|
require File.join(File.dirname(__FILE__), 'lib', 'right_http_connection')
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
GEM_NAME = 'right_http_connection' # what ppl will type to install your gem
|
18
|
-
RUBYFORGE_PROJECT = 'rightaws' # The unix name for your project
|
19
|
-
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
-
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
21
|
-
|
22
|
-
NAME = "right_http_connection"
|
23
|
-
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
24
|
-
VERS = RightHttpConnection::VERSION::STRING + (REV ? ".#{REV}" : "")
|
25
|
-
CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
|
26
|
-
RDOC_OPTS = ['--quiet', '--title', 'right_http_connection documentation',
|
27
|
-
"--opname", "index.html",
|
28
|
-
"--line-numbers",
|
29
|
-
"--main", "README",
|
30
|
-
"--inline-source"]
|
31
|
-
|
32
|
-
class Hoe
|
33
|
-
def extra_deps
|
34
|
-
@extra_deps.reject { |x| Array(x).first == 'hoe' }
|
35
|
-
end
|
36
|
-
end
|
17
|
+
Bundler::GemHelper.install_tasks
|
18
|
+
|
19
|
+
=begin
|
37
20
|
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
p.summary = DESCRIPTION
|
45
|
-
p.url = HOMEPATH
|
46
|
-
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
47
|
-
p.test_globs = ["test/**/test_*.rb"]
|
48
|
-
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
49
|
-
p.remote_rdoc_dir = "right_http_gem_doc"
|
50
|
-
|
51
|
-
# == Optional
|
52
|
-
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
53
|
-
#p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
|
54
|
-
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
21
|
+
# == Gem == #
|
22
|
+
|
23
|
+
gemtask = Rake::GemPackageTask.new(Gem::Specification.load("right_http_connection.gemspec")) do |package|
|
24
|
+
package.package_dir = ENV['PACKAGE_DIR'] || 'pkg'
|
25
|
+
package.need_zip = true
|
26
|
+
package.need_tar = true
|
55
27
|
end
|
56
28
|
|
29
|
+
directory gemtask.package_dir
|
30
|
+
|
31
|
+
CLEAN.include(gemtask.package_dir)
|
57
32
|
|
58
33
|
desc 'Generate website files'
|
59
34
|
task :website_generate do
|
@@ -88,3 +63,30 @@ task :check_version do
|
|
88
63
|
exit
|
89
64
|
end
|
90
65
|
end
|
66
|
+
=end
|
67
|
+
|
68
|
+
task :default => 'spec'
|
69
|
+
|
70
|
+
# == Unit Tests == #
|
71
|
+
|
72
|
+
desc "Run unit tests"
|
73
|
+
RSpec::Core::RakeTask.new
|
74
|
+
|
75
|
+
namespace :spec do
|
76
|
+
desc "Run unit tests with RCov"
|
77
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
78
|
+
t.rcov = true
|
79
|
+
t.rcov_opts = %q[--exclude "spec"]
|
80
|
+
end
|
81
|
+
|
82
|
+
desc "Print Specdoc for unit tests"
|
83
|
+
RSpec::Core::RakeTask.new(:doc) do |t|
|
84
|
+
t.rspec_opts = ["--format", "documentation"]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# == Functional tests == #
|
89
|
+
desc "Run functional tests"
|
90
|
+
Cucumber::Rake::Task.new do |t|
|
91
|
+
t.cucumber_opts = %w{--color --format pretty}
|
92
|
+
end
|
@@ -25,6 +25,7 @@
|
|
25
25
|
# Net::HTTP and Net::HTTPGenericRequest fixes to support 100-continue on
|
26
26
|
# POST and PUT. The request must have 'expect' field set to '100-continue'.
|
27
27
|
|
28
|
+
require 'timeout'
|
28
29
|
|
29
30
|
module Net
|
30
31
|
|
@@ -48,7 +49,7 @@ module Net
|
|
48
49
|
end
|
49
50
|
|
50
51
|
def rbuf_fill
|
51
|
-
timeout(@read_timeout) {
|
52
|
+
Timeout.timeout(@read_timeout) {
|
52
53
|
@rbuf << @io.sysread(@@socket_read_size)
|
53
54
|
}
|
54
55
|
end
|
@@ -91,18 +92,30 @@ module Net
|
|
91
92
|
private
|
92
93
|
|
93
94
|
def send_request_with_body(sock, ver, path, body, send_only=nil)
|
94
|
-
self.content_length = body.length
|
95
|
+
self.content_length = body.respond_to?(:bytesize) ? body.bytesize : body.length
|
95
96
|
delete 'Transfer-Encoding'
|
96
97
|
supply_default_content_type
|
97
98
|
write_header(sock, ver, path) unless send_only == :body
|
98
|
-
sock.write(body)
|
99
|
+
sock.write(body && body.to_s) unless send_only == :header
|
99
100
|
end
|
100
101
|
|
101
102
|
def send_request_with_body_stream(sock, ver, path, f, send_only=nil)
|
103
|
+
# KD: Fix 'content-length': it must not be greater than a piece of file left to be read.
|
104
|
+
# Otherwise the connection may behave like crazy causing 4xx or 5xx responses
|
105
|
+
#
|
106
|
+
# Only do this helpful thing if the stream responds to :pos (it may be something
|
107
|
+
# that responds to :read and :size but not :pos).
|
108
|
+
if f.respond_to?(:pos)
|
109
|
+
file_size = f.respond_to?(:lstat) ? f.lstat.size : f.size
|
110
|
+
bytes_to_read = [ file_size - f.pos, self.content_length.to_i ].sort.first
|
111
|
+
self.content_length = bytes_to_read
|
112
|
+
end
|
113
|
+
|
102
114
|
unless content_length() or chunked?
|
103
115
|
raise ArgumentError,
|
104
116
|
"Content-Length not given and Transfer-Encoding is not `chunked'"
|
105
117
|
end
|
118
|
+
bytes_to_read ||= content_length()
|
106
119
|
supply_default_content_type
|
107
120
|
write_header(sock, ver, path) unless send_only == :body
|
108
121
|
unless send_only == :header
|
@@ -112,8 +125,14 @@ module Net
|
|
112
125
|
end
|
113
126
|
sock.write "0\r\n\r\n"
|
114
127
|
else
|
115
|
-
|
128
|
+
# KD: When we read/write over file EOF it sometimes make the connection unstable
|
129
|
+
read_size = [ @@local_read_size, bytes_to_read ].sort.first
|
130
|
+
while s = f.read(read_size)
|
116
131
|
sock.write s
|
132
|
+
# Make sure we do not read over EOF or more than expected content-length
|
133
|
+
bytes_to_read -= read_size
|
134
|
+
break if bytes_to_read <= 0
|
135
|
+
read_size = bytes_to_read if bytes_to_read < read_size
|
117
136
|
end
|
118
137
|
end
|
119
138
|
end
|
@@ -144,6 +163,7 @@ module Net
|
|
144
163
|
req.exec @socket, @curr_http_version, edit_path(req.path), send_only
|
145
164
|
begin
|
146
165
|
res = HTTPResponse.read_new(@socket)
|
166
|
+
res.decode_content = req.decode_content if RUBY_VERSION > '2.0'
|
147
167
|
# if we expected 100-continue then send a body
|
148
168
|
if res.is_a?(HTTPContinue) && send_only && req['content-length'].to_i > 0
|
149
169
|
req.exec @socket, @curr_http_version, edit_path(req.path), :body
|
data/lib/base/support.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# These are ActiveSupport-;like extensions to do a few handy things in the gems
|
2
|
+
# Derived from ActiveSupport, so the AS copyright notice applies:
|
3
|
+
#
|
4
|
+
#
|
5
|
+
#
|
6
|
+
# Copyright (c) 2005 David Heinemeier Hansson
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
9
|
+
# a copy of this software and associated documentation files (the
|
10
|
+
# "Software"), to deal in the Software without restriction, including
|
11
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
12
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
13
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
14
|
+
# the following conditions:
|
15
|
+
#
|
16
|
+
# The above copyright notice and this permission notice shall be
|
17
|
+
# included in all copies or substantial portions of the Software.
|
18
|
+
#
|
19
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
20
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
21
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
22
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
23
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
24
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
25
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
26
|
+
#++
|
27
|
+
#
|
28
|
+
#
|
29
|
+
class String #:nodoc:
|
30
|
+
|
31
|
+
# Constantize tries to find a declared constant with the name specified
|
32
|
+
# in the string. It raises a NameError when the name is not in CamelCase
|
33
|
+
# or is not initialized.
|
34
|
+
#
|
35
|
+
# Examples
|
36
|
+
# "Module".constantize #=> Module
|
37
|
+
# "Class".constantize #=> Class
|
38
|
+
def right_constantize()
|
39
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
|
40
|
+
raise NameError, "#{self.inspect} is not a valid constant name!"
|
41
|
+
end
|
42
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
43
|
+
end
|
44
|
+
|
45
|
+
def right_camelize()
|
46
|
+
self.dup.split(/_/).map{ |word| word.capitalize }.join('')
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
class Object #:nodoc:
|
53
|
+
# "", " ", nil, [], and {} are blank
|
54
|
+
def right_blank?
|
55
|
+
if respond_to?(:empty?) && respond_to?(:strip)
|
56
|
+
empty? or strip.empty?
|
57
|
+
elsif respond_to?(:empty?)
|
58
|
+
empty?
|
59
|
+
else
|
60
|
+
!self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class NilClass #:nodoc:
|
66
|
+
def right_blank?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class FalseClass #:nodoc:
|
72
|
+
def right_blank?
|
73
|
+
true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class TrueClass #:nodoc:
|
78
|
+
def right_blank?
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Array #:nodoc:
|
84
|
+
alias_method :right_blank?, :empty?
|
85
|
+
end
|
86
|
+
|
87
|
+
class Hash #:nodoc:
|
88
|
+
alias_method :right_blank?, :empty?
|
89
|
+
|
90
|
+
# Return a new hash with all keys converted to symbols.
|
91
|
+
def right_symbolize_keys
|
92
|
+
inject({}) do |options, (key, value)|
|
93
|
+
options[key.to_sym] = value
|
94
|
+
options
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class String #:nodoc:
|
100
|
+
def right_blank?
|
101
|
+
empty? || strip.empty?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Numeric #:nodoc:
|
106
|
+
def right_blank?
|
107
|
+
false
|
108
|
+
end
|
109
|
+
end
|
data/lib/base/version.rb
ADDED
@@ -0,0 +1,32 @@
|
|
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
|
+
module RightHttpConnection #:nodoc:
|
25
|
+
module VERSION #:nodoc:
|
26
|
+
MAJOR = 1 unless defined?(MAJOR)
|
27
|
+
MINOR = 5 unless defined?(MINOR)
|
28
|
+
TINY = 1 unless defined?(TINY)
|
29
|
+
|
30
|
+
STRING = [MAJOR, MINOR, TINY].join('.') unless defined?(STRING)
|
31
|
+
end
|
32
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2007-
|
2
|
+
# Copyright (c) 2007-2011 RightScale Inc
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -27,22 +27,12 @@ require "time"
|
|
27
27
|
require "logger"
|
28
28
|
|
29
29
|
$:.unshift(File.dirname(__FILE__))
|
30
|
-
require
|
31
|
-
|
32
|
-
|
33
|
-
module RightHttpConnection #:nodoc:
|
34
|
-
module VERSION #:nodoc:
|
35
|
-
MAJOR = 1
|
36
|
-
MINOR = 2
|
37
|
-
TINY = 3
|
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
|
+
|
46
36
|
=begin rdoc
|
47
37
|
HttpConnection maintains a persistent HTTP connection to a remote
|
48
38
|
server. Each instance maintains its own unique connection to the
|
@@ -75,16 +65,16 @@ the full number of potential reconnects and retries available to
|
|
75
65
|
them.
|
76
66
|
=end
|
77
67
|
|
78
|
-
class HttpConnection
|
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
|
86
|
-
# Length of the post-error probationary period during which all requests will fail
|
87
|
-
HTTP_CONNECTION_RETRY_DELAY = 15
|
75
|
+
HTTP_CONNECTION_READ_TIMEOUT = 120 unless defined?(HTTP_CONNECTION_READ_TIMEOUT)
|
76
|
+
# Length of the post-error probationary period during which all requests will fail
|
77
|
+
HTTP_CONNECTION_RETRY_DELAY = 15 unless defined?(HTTP_CONNECTION_RETRY_DELAY)
|
88
78
|
|
89
79
|
#--------------------
|
90
80
|
# class methods
|
@@ -95,22 +85,28 @@ them.
|
|
95
85
|
@@params[:http_connection_open_timeout] = HTTP_CONNECTION_OPEN_TIMEOUT
|
96
86
|
@@params[:http_connection_read_timeout] = HTTP_CONNECTION_READ_TIMEOUT
|
97
87
|
@@params[:http_connection_retry_delay] = HTTP_CONNECTION_RETRY_DELAY
|
98
|
-
|
88
|
+
|
99
89
|
# Query the global (class-level) parameters:
|
100
|
-
#
|
101
|
-
# :user_agent => 'www.HostName.com' # String to report as HTTP User agent
|
90
|
+
#
|
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
|
113
|
-
|
109
|
+
|
114
110
|
# Set the global (class-level) parameters
|
115
111
|
def self.params=(params)
|
116
112
|
@@params = params
|
@@ -125,30 +121,65 @@ them.
|
|
125
121
|
attr_accessor :logger
|
126
122
|
|
127
123
|
# Params hash:
|
128
|
-
# :user_agent => 'www.HostName.com' # String to report as HTTP User agent
|
124
|
+
# :user_agent => 'www.HostName.com' # String to report as HTTP User agent
|
129
125
|
# :ca_file => 'path_to_file' # A path of a CA certification file in PEM format. The file can contain several CA certificates.
|
126
|
+
# :fail_if_ca_mismatch => Boolean # If ca_file is set and the server certificate doesn't verify, a log line is generated regardless, but normally right_http_connection continues on past the failure. If this is set, fail to connect in that case. Defaults to false.
|
130
127
|
# :logger => Logger object # If omitted, HttpConnection logs to STDOUT
|
131
128
|
# :exception => Exception to raise # The type of exception to raise if a request repeatedly fails. RuntimeError is raised if this parameter is omitted.
|
129
|
+
# :proxy_host => 'hostname' # hostname of HTTP proxy host to use, default none.
|
130
|
+
# :proxy_port => port # port of HTTP proxy host to use, default none.
|
131
|
+
# :proxy_username => 'username' # username to use for proxy authentication, default none.
|
132
|
+
# :proxy_password => 'password' # password to use for proxy authentication, default none.
|
132
133
|
# :http_connection_retry_count # by default == Rightscale::HttpConnection.params[:http_connection_retry_count]
|
133
134
|
# :http_connection_open_timeout # by default == Rightscale::HttpConnection.params[:http_connection_open_timeout]
|
134
135
|
# :http_connection_read_timeout # by default == Rightscale::HttpConnection.params[:http_connection_read_timeout]
|
135
136
|
# :http_connection_retry_delay # by default == Rightscale::HttpConnection.params[:http_connection_retry_delay]
|
136
|
-
#
|
137
|
+
# :raise_on_timeout # do not perform a retry if timeout is received (false by default)
|
137
138
|
def initialize(params={})
|
138
139
|
@params = params
|
140
|
+
|
141
|
+
#set up logging first
|
142
|
+
@logger = get_param(:logger) ||
|
143
|
+
(RAILS_DEFAULT_LOGGER if defined?(RAILS_DEFAULT_LOGGER)) ||
|
144
|
+
Logger.new(STDOUT)
|
145
|
+
|
146
|
+
env_proxy_host, env_proxy_port, env_proxy_username, env_proxy_password = get_proxy_info_for_env if ENV['HTTP_PROXY']
|
147
|
+
|
139
148
|
@params[:http_connection_retry_count] ||= @@params[:http_connection_retry_count]
|
140
149
|
@params[:http_connection_open_timeout] ||= @@params[:http_connection_open_timeout]
|
141
150
|
@params[:http_connection_read_timeout] ||= @@params[:http_connection_read_timeout]
|
142
151
|
@params[:http_connection_retry_delay] ||= @@params[:http_connection_retry_delay]
|
152
|
+
@params[:proxy_host] ||= @@params[:proxy_host] || env_proxy_host
|
153
|
+
@params[:proxy_port] ||= @@params[:proxy_port] || env_proxy_port
|
154
|
+
@params[:proxy_username] ||= @@params[:proxy_username] || env_proxy_username
|
155
|
+
@params[:proxy_password] ||= @@params[:proxy_password] || env_proxy_password
|
156
|
+
|
143
157
|
@http = nil
|
144
158
|
@server = nil
|
145
|
-
|
146
|
-
|
147
|
-
|
159
|
+
#--------------
|
160
|
+
# Retry state - Keep track of errors on a per-server basis
|
161
|
+
#--------------
|
162
|
+
@state = {} # retry state indexed by server: consecutive error count, error time, and error
|
163
|
+
|
164
|
+
@eof = {}
|
165
|
+
end
|
166
|
+
|
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
|
148
178
|
end
|
179
|
+
private :get_proxy_info_for_env
|
149
180
|
|
150
|
-
def get_param(name)
|
151
|
-
@params[name] || @@params[name]
|
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
|
@@ -164,7 +195,7 @@ them.
|
|
164
195
|
def socket_read_size=(newsize)
|
165
196
|
Net::BufferedIO.socket_read_size=(newsize)
|
166
197
|
end
|
167
|
-
|
198
|
+
|
168
199
|
# Query for the maximum size (in bytes) of a single read from local data
|
169
200
|
# sources like files. This is important, for example, in a streaming PUT of a
|
170
201
|
# large buffer.
|
@@ -180,81 +211,78 @@ them.
|
|
180
211
|
end
|
181
212
|
|
182
213
|
private
|
183
|
-
#--------------
|
184
|
-
# Retry state - Keep track of errors on a per-server basis
|
185
|
-
#--------------
|
186
|
-
@@state = {} # retry state indexed by server: consecutive error count, error time, and error
|
187
|
-
@@eof = {}
|
188
214
|
|
189
215
|
# number of consecutive errors seen for server, 0 all is ok
|
190
216
|
def error_count
|
191
|
-
|
217
|
+
@state[@server] ? @state[@server][:count] : 0
|
192
218
|
end
|
193
|
-
|
219
|
+
|
194
220
|
# time of last error for server, nil if all is ok
|
195
221
|
def error_time
|
196
|
-
|
222
|
+
@state[@server] && @state[@server][:time]
|
197
223
|
end
|
198
|
-
|
224
|
+
|
199
225
|
# message for last error for server, "" if all is ok
|
200
226
|
def error_message
|
201
|
-
|
227
|
+
@state[@server] ? @state[@server][:message] : ""
|
202
228
|
end
|
203
|
-
|
229
|
+
|
204
230
|
# add an error for a server
|
205
|
-
def error_add(
|
206
|
-
|
231
|
+
def error_add(error)
|
232
|
+
message = error
|
233
|
+
message = "#{error.class.name}: #{error.message}" if error.is_a?(Exception)
|
234
|
+
@state[@server] = { :count => error_count+1, :time => Time.now, :message => message }
|
207
235
|
end
|
208
|
-
|
236
|
+
|
209
237
|
# reset the error state for a server (i.e. a request succeeded)
|
210
238
|
def error_reset
|
211
|
-
|
239
|
+
@state.delete(@server)
|
212
240
|
end
|
213
|
-
|
241
|
+
|
214
242
|
# Error message stuff...
|
215
243
|
def banana_message
|
216
|
-
return "#{@server} temporarily unavailable: (#{error_message})"
|
244
|
+
return "#{@protocol}://#{@server}:#{@port} temporarily unavailable: (#{error_message})"
|
217
245
|
end
|
218
246
|
|
219
247
|
def err_header
|
220
248
|
return "#{self.class.name} :"
|
221
249
|
end
|
222
|
-
|
250
|
+
|
223
251
|
# Adds new EOF timestamp.
|
224
252
|
# Returns the number of seconds to wait before new conection retry:
|
225
253
|
# 0.5, 1, 2, 4, 8
|
226
254
|
def add_eof
|
227
|
-
(
|
228
|
-
0.25 * 2 **
|
255
|
+
(@eof[@server] ||= []).unshift Time.now
|
256
|
+
0.25 * 2 ** @eof[@server].size
|
229
257
|
end
|
230
258
|
|
231
259
|
# Returns first EOF timestamp or nul if have no EOFs being tracked.
|
232
260
|
def eof_time
|
233
|
-
|
261
|
+
@eof[@server] && @eof[@server].last
|
234
262
|
end
|
235
|
-
|
263
|
+
|
236
264
|
# Returns true if we are receiving EOFs during last @params[:http_connection_retry_delay] seconds
|
237
265
|
# and there were no successful response from server
|
238
266
|
def raise_on_eof_exception?
|
239
|
-
|
240
|
-
end
|
241
|
-
|
267
|
+
@eof[@server].nil? ? false : ( (Time.now.to_i-@params[:http_connection_retry_delay]) > @eof[@server].last.to_i )
|
268
|
+
end
|
269
|
+
|
242
270
|
# Reset a list of EOFs for this server.
|
243
271
|
# This is being called when we have got an successful response from server.
|
244
272
|
def eof_reset
|
245
|
-
|
273
|
+
@eof.delete(@server)
|
246
274
|
end
|
247
|
-
|
248
|
-
# Detects if an object is 'streamable' - can we read from it, and can we know the size?
|
275
|
+
|
276
|
+
# Detects if an object is 'streamable' - can we read from it, and can we know the size?
|
249
277
|
def setup_streaming(request)
|
250
278
|
if(request.body && request.body.respond_to?(:read))
|
251
279
|
body = request.body
|
252
|
-
request.content_length = body.respond_to?(:lstat) ? body.lstat.size : body.size
|
280
|
+
request.content_length = body.respond_to?(:lstat) ? body.lstat.size : body.size
|
253
281
|
request.body_stream = request.body
|
254
282
|
true
|
255
283
|
end
|
256
284
|
end
|
257
|
-
|
285
|
+
|
258
286
|
def get_fileptr_offset(request_params)
|
259
287
|
request_params[:request].body.pos
|
260
288
|
rescue Exception => e
|
@@ -262,7 +290,7 @@ them.
|
|
262
290
|
# Just return 0 and get on with life.
|
263
291
|
0
|
264
292
|
end
|
265
|
-
|
293
|
+
|
266
294
|
def reset_fileptr_offset(request, offset = 0)
|
267
295
|
if(request.body_stream && request.body_stream.respond_to?(:pos))
|
268
296
|
begin
|
@@ -272,38 +300,88 @@ them.
|
|
272
300
|
" -- #{err_header} #{e.inspect}")
|
273
301
|
raise e
|
274
302
|
end
|
275
|
-
end
|
303
|
+
end
|
276
304
|
end
|
277
305
|
|
306
|
+
SECURITY_PARAMS = [:cert, :key, :cert_file, :key_file, :ca_file]
|
307
|
+
|
278
308
|
# Start a fresh connection. The object closes any existing connection and
|
279
309
|
# opens a new one.
|
280
310
|
def start(request_params)
|
281
311
|
# close the previous if exists
|
282
312
|
finish
|
283
313
|
# create new connection
|
284
|
-
@server
|
285
|
-
@port
|
286
|
-
@protocol
|
314
|
+
@server = request_params[:server]
|
315
|
+
@port = request_params[:port]
|
316
|
+
@protocol = request_params[:protocol]
|
317
|
+
@proxy_host = request_params[:proxy_host]
|
318
|
+
@proxy_port = request_params[:proxy_port]
|
319
|
+
@proxy_username = request_params[:proxy_username]
|
320
|
+
@proxy_password = request_params[:proxy_password]
|
287
321
|
|
322
|
+
SECURITY_PARAMS.each do |param_name|
|
323
|
+
@params[param_name] = request_params[param_name]
|
324
|
+
end
|
325
|
+
|
288
326
|
@logger.info("Opening new #{@protocol.upcase} connection to #@server:#@port")
|
289
|
-
|
290
|
-
@
|
291
|
-
|
292
|
-
|
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)
|
335
|
+
|
293
336
|
if @protocol == 'https'
|
294
337
|
verifyCallbackProc = Proc.new{ |ok, x509_store_ctx|
|
338
|
+
# List of error codes: http://www.openssl.org/docs/apps/verify.html
|
295
339
|
code = x509_store_ctx.error
|
296
340
|
msg = x509_store_ctx.error_string
|
297
|
-
|
298
|
-
|
299
|
-
|
341
|
+
if request_params[:fail_if_ca_mismatch] && code != 0
|
342
|
+
false
|
343
|
+
else
|
344
|
+
true
|
345
|
+
end
|
300
346
|
}
|
301
347
|
@http.use_ssl = true
|
348
|
+
|
302
349
|
ca_file = get_param(:ca_file)
|
303
|
-
if ca_file
|
304
|
-
|
350
|
+
if ca_file && File.exists?(ca_file)
|
351
|
+
# Documentation for 'http.rb':
|
352
|
+
# : verify_mode, verify_mode=((|mode|))
|
353
|
+
# Sets the flags for server the certification verification at
|
354
|
+
# beginning of SSL/TLS session.
|
355
|
+
# OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable.
|
356
|
+
#
|
357
|
+
# KHRVI: looks like the constant VERIFY_FAIL_IF_NO_PEER_CERT is not acceptable
|
305
358
|
@http.verify_callback = verifyCallbackProc
|
306
|
-
@http.ca_file
|
359
|
+
@http.ca_file= ca_file
|
360
|
+
@http.verify_mode = get_param(:use_server_auth) ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
361
|
+
# The depth count is 'level 0:peer certificate', 'level 1: CA certificate', 'level 2: higher level CA certificate', and so on.
|
362
|
+
# Setting the maximum depth to 2 allows the levels 0, 1, and 2. The default depth limit is 9, allowing for the peer certificate and additional 9 CA certificates.
|
363
|
+
@http.verify_depth = 9
|
364
|
+
else
|
365
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
366
|
+
end
|
367
|
+
|
368
|
+
# CERT
|
369
|
+
cert_file = get_param(:cert_file, request_params)
|
370
|
+
cert = File.read(cert_file) if cert_file && File.exists?(cert_file)
|
371
|
+
cert ||= get_param(:cert, request_params)
|
372
|
+
# KEY
|
373
|
+
key_file = get_param(:key_file, request_params)
|
374
|
+
key = File.read(key_file) if key_file && File.exists?(key_file)
|
375
|
+
key ||= get_param(:key, request_params)
|
376
|
+
if cert && key
|
377
|
+
begin
|
378
|
+
@http.verify_callback = verifyCallbackProc
|
379
|
+
@http.cert = OpenSSL::X509::Certificate.new(cert)
|
380
|
+
@http.key = OpenSSL::PKey::RSA.new(key)
|
381
|
+
rescue OpenSSL::PKey::RSAError, OpenSSL::X509::CertificateError => e
|
382
|
+
@logger.error "##### Error loading SSL client cert or key: #{e.message} :: backtrace #{e.backtrace}"
|
383
|
+
raise e
|
384
|
+
end
|
307
385
|
end
|
308
386
|
end
|
309
387
|
# open connection
|
@@ -312,55 +390,82 @@ them.
|
|
312
390
|
|
313
391
|
public
|
314
392
|
|
315
|
-
=begin rdoc
|
393
|
+
=begin rdoc
|
316
394
|
Send HTTP request to server
|
317
395
|
|
318
396
|
request_params hash:
|
319
397
|
: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
|
398
|
+
:port => '80' # Port of HTTP server
|
399
|
+
:protocol => 'https' # http and https are supported on any port
|
322
400
|
:request => 'requeststring' # Fully-formed HTTP request to make
|
401
|
+
:proxy_host => 'hostname' # hostname of HTTP proxy host to use, default none.
|
402
|
+
:proxy_port => port # port of HTTP proxy host to use, default none.
|
403
|
+
:proxy_username => 'username' # username to use for proxy authentication, default none.
|
404
|
+
:proxy_password => 'password' # password to use for proxy authentication, default none.
|
405
|
+
|
406
|
+
:raise_on_timeout # do not perform a retry if timeout is received (false by default)
|
407
|
+
:http_connection_retry_count
|
408
|
+
:http_connection_open_timeout
|
409
|
+
:http_connection_read_timeout
|
410
|
+
:http_connection_retry_delay
|
411
|
+
:user_agent
|
412
|
+
:exception
|
323
413
|
|
324
414
|
Raises RuntimeError, Interrupt, and params[:exception] (if specified in new).
|
325
|
-
|
415
|
+
|
326
416
|
=end
|
327
417
|
def request(request_params, &block)
|
418
|
+
current_params = @params.merge(request_params)
|
419
|
+
exception = get_param(:exception, current_params) || RuntimeError
|
420
|
+
|
421
|
+
# Re-establish the connection if any of auth params has changed
|
422
|
+
same_auth_params_as_before = SECURITY_PARAMS.select do |param|
|
423
|
+
request_params[param] != get_param(param)
|
424
|
+
end.empty?
|
425
|
+
|
328
426
|
# We save the offset here so that if we need to retry, we can return the file pointer to its initial position
|
329
|
-
mypos = get_fileptr_offset(
|
427
|
+
mypos = get_fileptr_offset(current_params)
|
330
428
|
loop do
|
429
|
+
|
430
|
+
current_params[:protocol] ||= (current_params[:port] == 443 ? 'https' : 'http')
|
431
|
+
# (re)open connection to server if none exists or params has changed
|
432
|
+
same_server_as_before = @server == current_params[:server] &&
|
433
|
+
@port == current_params[:port] &&
|
434
|
+
@protocol == current_params[:protocol] &&
|
435
|
+
same_auth_params_as_before
|
436
|
+
|
331
437
|
# if we are inside a delay between retries: no requests this time!
|
332
|
-
if
|
333
|
-
|
438
|
+
# (skip this step if the endpoint has changed)
|
439
|
+
if error_count > current_params[:http_connection_retry_count] &&
|
440
|
+
error_time + current_params[:http_connection_retry_delay] > Time.now &&
|
441
|
+
same_server_as_before
|
442
|
+
|
334
443
|
# store the message (otherwise it will be lost after error_reset and
|
335
444
|
# we will raise an exception with an empty text)
|
336
445
|
banana_message_text = banana_message
|
337
446
|
@logger.warn("#{err_header} re-raising same error: #{banana_message_text} " +
|
338
|
-
"-- error count: #{error_count}, error age: #{Time.now.to_i - error_time.to_i}")
|
339
|
-
exception = get_param(:exception) || RuntimeError
|
447
|
+
"-- error count: #{error_count}, error age: #{Time.now.to_i - error_time.to_i}")
|
340
448
|
raise exception.new(banana_message_text)
|
341
449
|
end
|
342
|
-
|
450
|
+
|
343
451
|
# try to connect server(if connection does not exist) and get response data
|
344
452
|
begin
|
345
|
-
|
346
|
-
|
347
|
-
unless @http &&
|
453
|
+
request = current_params[:request]
|
454
|
+
request['User-Agent'] = get_param(:user_agent, current_params) || ''
|
455
|
+
unless @http &&
|
348
456
|
@http.started? &&
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
start(request_params)
|
457
|
+
same_server_as_before
|
458
|
+
same_auth_params_as_before = true
|
459
|
+
start(current_params)
|
353
460
|
end
|
354
|
-
|
355
|
-
# get response and return it
|
356
|
-
request = request_params[:request]
|
357
|
-
request['User-Agent'] = get_param(:user_agent) || ''
|
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
|
365
470
|
eof_reset
|
366
471
|
return response
|
@@ -373,51 +478,64 @@ them.
|
|
373
478
|
# 'slept'. It is still not clear which way we should treat errors
|
374
479
|
# like RST and resolution failures. For now, there is no additional
|
375
480
|
# delay for these errors although this may change in the future.
|
376
|
-
|
481
|
+
|
377
482
|
# EOFError means the server closed the connection on us.
|
378
483
|
rescue EOFError => e
|
379
|
-
|
380
|
-
@http = nil
|
484
|
+
finish(e.message)
|
381
485
|
|
486
|
+
@logger.debug("#{err_header} server #{@server} closed connection")
|
487
|
+
|
382
488
|
# if we have waited long enough - raise an exception...
|
383
489
|
if raise_on_eof_exception?
|
384
|
-
exception
|
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}")
|
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
|
388
493
|
# ... else just sleep a bit before new retry
|
389
494
|
sleep(add_eof)
|
390
495
|
# We will be retrying the request, so reset the file pointer
|
391
496
|
reset_fileptr_offset(request, mypos)
|
392
|
-
end
|
393
|
-
rescue
|
394
|
-
|
395
|
-
|
396
|
-
if e.
|
397
|
-
@logger.debug( "#{err_header} request to server #{@server} interrupted by ctrl-c")
|
398
|
-
raise
|
399
|
-
elsif e.is_a?(ArgumentError) && e.message.include?('wrong number of arguments (5 for 4)')
|
497
|
+
end
|
498
|
+
rescue ArgumentError => e
|
499
|
+
finish(e.message)
|
500
|
+
|
501
|
+
if e.message.include?('wrong number of arguments (5 for 4)')
|
400
502
|
# seems our net_fix patch was overriden...
|
401
|
-
exception = get_param(:exception) || RuntimeError
|
402
503
|
raise exception.new('incompatible Net::HTTP monkey-patch')
|
504
|
+
else
|
505
|
+
raise e
|
506
|
+
end
|
507
|
+
|
508
|
+
rescue Timeout::Error, SocketError, SystemCallError, Interrupt => e # See comment at bottom for the list of errors seen...
|
509
|
+
finish(e.message)
|
510
|
+
if e.is_a?(Errno::ETIMEDOUT) || e.is_a?(Timeout::Error)
|
511
|
+
# Omit retries if it was explicitly requested
|
512
|
+
# #6481:
|
513
|
+
# ... When creating a resource in EC2 (instance, volume, snapshot, etc) it is undetermined what happened if the call times out.
|
514
|
+
# The resource may or may not have been created in EC2. Retrying the call may cause multiple resources to be created...
|
515
|
+
raise exception.new("#{e.class.name}: #{e.message}") if current_params[:raise_on_timeout]
|
516
|
+
elsif e.is_a?(Interrupt)
|
517
|
+
# if ctrl+c is pressed - we have to reraise exception to terminate proggy
|
518
|
+
@logger.debug( "#{err_header} request to server #{@server} interrupted by ctrl-c")
|
519
|
+
raise e
|
403
520
|
end
|
404
521
|
# oops - we got a banana: log it
|
405
|
-
error_add(e
|
522
|
+
error_add(e)
|
406
523
|
@logger.warn("#{err_header} request failure count: #{error_count}, exception: #{e.inspect}")
|
407
524
|
|
408
525
|
# We will be retrying the request, so reset the file pointer
|
409
526
|
reset_fileptr_offset(request, mypos)
|
410
|
-
|
411
527
|
end
|
412
528
|
end
|
413
529
|
end
|
414
530
|
|
415
531
|
def finish(reason = '')
|
416
532
|
if @http && @http.started?
|
417
|
-
reason = ", reason: '#{reason}'" unless reason.
|
533
|
+
reason = ", reason: '#{reason}'" unless reason.empty?
|
418
534
|
@logger.info("Closing #{@http.use_ssl? ? 'HTTPS' : 'HTTP'} connection to #{@http.address}:#{@http.port}#{reason}")
|
419
|
-
@http.finish
|
535
|
+
@http.finish
|
420
536
|
end
|
537
|
+
ensure
|
538
|
+
@http = nil
|
421
539
|
end
|
422
540
|
|
423
541
|
# Errors received during testing:
|
@@ -430,6 +548,6 @@ them.
|
|
430
548
|
# #<Errno::ECONNRESET: Connection reset by peer>
|
431
549
|
# #<OpenSSL::SSL::SSLError: SSL_write:: bad write retry>
|
432
550
|
end
|
433
|
-
|
551
|
+
|
434
552
|
end
|
435
553
|
|