/
http_client.rb
209 lines (180 loc) · 6.61 KB
/
http_client.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
require 'pact_broker/client/retry'
require 'pact_broker/client/hal/authorization_header_redactor'
require 'net/http'
require 'json'
require 'openssl'
module PactBroker
module Client
module Hal
class HttpClient
RETRYABLE_ERRORS = [Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EHOSTUNREACH, Net::ReadTimeout]
attr_accessor :username, :password, :verbose, :token
def initialize options
@username = options[:username]
@password = options[:password]
@verbose = options[:verbose]
@token = options[:token]
end
def get href, params = {}, headers = {}
query = build_nested_query(params)
uri = URI(href)
uri.query = query
perform_request(create_request(uri, 'Get', nil, headers), uri)
end
def put href, body = nil, headers = {}
uri = URI(href)
perform_request(create_request(uri, 'Put', body, headers), uri)
end
def post href, body = nil, headers = {}
uri = URI(href)
perform_request(create_request(uri, 'Post', body, headers), uri)
end
def patch href, body = nil, headers = {}
uri = URI(href)
perform_request(create_request(uri, 'Patch', body, headers), uri)
end
def delete href, body = nil, headers = {}
uri = URI(href)
perform_request(create_request(uri, 'Delete', body, headers), uri)
end
def create_request uri, http_method, body = nil, headers = {}
request = Net::HTTP.const_get(http_method).new(uri.request_uri)
request['Content-Type'] ||= "application/json" if ['Post', 'Put'].include?(http_method)
request['Content-Type'] ||= "application/merge-patch+json" if ['Patch'].include?(http_method)
request['Accept'] = "application/hal+json"
request['Accept-Encoding'] = nil if verbose?
headers.each do | key, value |
request[key] = value
end
request.body = body if body
request.basic_auth username, password if username
request['Authorization'] = "Bearer #{token}" if token
request
end
def perform_request request, uri
response = until_truthy_or_max_times(condition: ->(resp) { resp.code.to_i < 500 }) do
http = Net::HTTP.new(uri.host, uri.port, :ENV)
http.set_debug_output(output_stream) if verbose?
http.use_ssl = (uri.scheme == 'https')
# Need to manually set the ca_file and ca_path for the pact-ruby-standalone.
# The env vars seem to be picked up automatically in later Ruby versions.
# See https://github.com/pact-foundation/pact-ruby-standalone/issues/57
http.ca_file = ENV['SSL_CERT_FILE'] if ENV['SSL_CERT_FILE'] && ENV['SSL_CERT_FILE'] != ''
http.ca_path = ENV['SSL_CERT_DIR'] if ENV['SSL_CERT_DIR'] && ENV['SSL_CERT_DIR'] != ''
if x509_certificate?
http.cert = OpenSSL::X509::Certificate.new(x509_client_cert_file)
http.key = OpenSSL::PKey::RSA.new(x509_client_key_file)
end
if disable_ssl_verification?
if verbose?
$stdout.puts("SSL verification is disabled")
end
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
http.start do |http|
http.request request
end
end
Response.new(response)
end
def until_truthy_or_max_times options = {}
max_tries = options.fetch(:times, default_max_tries)
tries = 0
sleep_interval = options.fetch(:sleep, 5)
sleep(sleep_interval) if options[:sleep_first]
while true
begin
result = yield
return result if max_tries < 2
if options[:condition]
condition_result = options[:condition].call(result)
return result if condition_result
else
return result if result
end
tries += 1
return result if max_tries == tries
sleep sleep_interval
rescue *RETRYABLE_ERRORS => e
tries += 1
$stderr.puts "ERROR: Error making request - #{e.class} #{e.message} #{e.backtrace.find{|l| l.include?('pact_broker-client')}}, attempt #{tries} of #{max_tries}"
raise e if max_tries == tries
sleep sleep_interval
end
end
end
def default_max_tries
5
end
def sleep seconds
Kernel.sleep seconds
end
def output_stream
AuthorizationHeaderRedactor.new($stdout)
end
def verbose?
verbose || ENV["VERBOSE"] == "true"
end
def x509_certificate?
ENV['X509_CLIENT_CERT_FILE'] && ENV['X509_CLIENT_CERT_FILE'] != '' &&
ENV['X509_CLIENT_KEY_FILE'] && ENV['X509_CLIENT_KEY_FILE'] != ''
end
def x509_client_cert_file
File.read(ENV['X509_CLIENT_CERT_FILE'])
end
def x509_client_key_file
File.read(ENV['X509_CLIENT_KEY_FILE'])
end
def disable_ssl_verification?
ENV['PACT_DISABLE_SSL_VERIFICATION'] == 'true' || ENV['PACT_BROKER_DISABLE_SSL_VERIFICATION'] == 'true'
end
# From Rack lib/rack/utils.rb
def build_nested_query(value, prefix = nil)
case value
when Array
value.map { |v|
build_nested_query(v, "#{prefix}[]")
}.join("&")
when Hash
value.map { |k, v|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
}.delete_if(&:empty?).join('&')
when nil
prefix
else
raise ArgumentError, "value must be a Hash" if prefix.nil?
"#{prefix}=#{escape(value)}"
end
end
def escape(s)
URI.encode_www_form_component(s)
end
class Response < SimpleDelegator
def body
bod = raw_body
if bod && bod != ''
JSON.parse(bod)
else
nil
end
end
def headers
__getobj__().to_hash
end
def header(name)
__getobj__()[name]
end
def raw_body
__getobj__().body
end
def status
code.to_i
end
def success?
__getobj__().code.start_with?("2")
end
end
end
end
end
end