right_http_connection 1.2.3 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|