spectre-core 1.12.0 → 1.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/spectre +516 -516
- data/lib/spectre/assertion.rb +10 -10
- data/lib/spectre/bag.rb +21 -21
- data/lib/spectre/curl.rb +397 -397
- data/lib/spectre/diagnostic.rb +39 -39
- data/lib/spectre/environment.rb +30 -30
- data/lib/spectre/helpers.rb +133 -133
- data/lib/spectre/http.rb +373 -364
- data/lib/spectre/logger/console.rb +143 -143
- data/lib/spectre/logger/file.rb +96 -96
- data/lib/spectre/logger.rb +146 -146
- data/lib/spectre/mixin.rb +58 -58
- data/lib/spectre/reporter/console.rb +101 -102
- data/lib/spectre/reporter/junit.rb +100 -100
- data/lib/spectre/resources.rb +49 -49
- data/lib/spectre.rb +447 -440
- metadata +12 -12
data/lib/spectre/curl.rb
CHANGED
@@ -1,397 +1,397 @@
|
|
1
|
-
require_relative '../spectre'
|
2
|
-
|
3
|
-
require 'open3'
|
4
|
-
require 'ostruct'
|
5
|
-
|
6
|
-
|
7
|
-
module Spectre::Curl
|
8
|
-
class SpectreHttpRequest < Spectre::DslClass
|
9
|
-
def initialize request
|
10
|
-
@__req = request
|
11
|
-
end
|
12
|
-
|
13
|
-
def method method_name
|
14
|
-
@__req['method'] = method_name.upcase
|
15
|
-
end
|
16
|
-
|
17
|
-
def url base_url
|
18
|
-
@__req['base_url'] = base_url
|
19
|
-
end
|
20
|
-
|
21
|
-
def path url_path
|
22
|
-
@__req['path'] = url_path
|
23
|
-
end
|
24
|
-
|
25
|
-
def header name, value
|
26
|
-
@__req['headers'] = [] unless @__req['headers']
|
27
|
-
@__req['headers'].append [name, value.to_s.strip]
|
28
|
-
end
|
29
|
-
|
30
|
-
def param name, value
|
31
|
-
@__req['query'] = [] unless @__req['query']
|
32
|
-
@__req['query'].append [name, value.to_s.strip]
|
33
|
-
end
|
34
|
-
|
35
|
-
def content_type media_type
|
36
|
-
@__req['headers'] = [] unless @__req['headers']
|
37
|
-
@__req['headers'].append ['Content-Type', media_type]
|
38
|
-
end
|
39
|
-
|
40
|
-
def json data
|
41
|
-
body JSON.pretty_generate(data)
|
42
|
-
content_type 'application/json'
|
43
|
-
end
|
44
|
-
|
45
|
-
def body body_content
|
46
|
-
@__req['body'] = body_content
|
47
|
-
end
|
48
|
-
|
49
|
-
def ensure_success!
|
50
|
-
@__req['ensure_success'] = true
|
51
|
-
end
|
52
|
-
|
53
|
-
def ensure_success?
|
54
|
-
@__req['ensure_success']
|
55
|
-
end
|
56
|
-
|
57
|
-
def authenticate method
|
58
|
-
@__req['auth'] = method
|
59
|
-
end
|
60
|
-
|
61
|
-
def certificate path
|
62
|
-
@__req['cert'] = path
|
63
|
-
use_ssl!
|
64
|
-
end
|
65
|
-
|
66
|
-
def use_ssl!
|
67
|
-
@__req['use_ssl'] = true
|
68
|
-
end
|
69
|
-
|
70
|
-
alias_method :auth, :authenticate
|
71
|
-
alias_method :cert, :certificate
|
72
|
-
alias_method :media_type, :content_type
|
73
|
-
end
|
74
|
-
|
75
|
-
class SpectreHttpHeader
|
76
|
-
def initialize headers
|
77
|
-
@headers = headers || {}
|
78
|
-
end
|
79
|
-
|
80
|
-
def [] key
|
81
|
-
@headers[key.downcase]
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
class SpectreHttpResponse
|
86
|
-
def initialize res
|
87
|
-
@res = res
|
88
|
-
@data = nil
|
89
|
-
end
|
90
|
-
|
91
|
-
def code
|
92
|
-
@res[:code]
|
93
|
-
end
|
94
|
-
|
95
|
-
def message
|
96
|
-
@res[:message]
|
97
|
-
end
|
98
|
-
|
99
|
-
def protocol
|
100
|
-
@res[:protocol]
|
101
|
-
end
|
102
|
-
|
103
|
-
def version
|
104
|
-
@res[:version]
|
105
|
-
end
|
106
|
-
|
107
|
-
def headers
|
108
|
-
SpectreHttpHeader.new @res[:headers]
|
109
|
-
end
|
110
|
-
|
111
|
-
def body
|
112
|
-
@res[:body]
|
113
|
-
end
|
114
|
-
|
115
|
-
def json
|
116
|
-
return nil unless @res[:body]
|
117
|
-
|
118
|
-
if @data == nil
|
119
|
-
begin
|
120
|
-
@data = JSON.parse(@res[:body], object_class: OpenStruct)
|
121
|
-
rescue
|
122
|
-
raise 'invalid json'
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
@data
|
127
|
-
end
|
128
|
-
|
129
|
-
def success?
|
130
|
-
@res[:code] < 400
|
131
|
-
end
|
132
|
-
|
133
|
-
def pretty
|
134
|
-
@res.pretty
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
# DEFAULT_HTTP_REQUEST = {
|
139
|
-
# 'method' => 'GET', # -X, --request <cmd>
|
140
|
-
# 'base_url' => nil,
|
141
|
-
# 'path' => nil,
|
142
|
-
# 'headers' => nil, # -H, --header <header/@file>
|
143
|
-
# 'query' => nil,
|
144
|
-
# 'body' => nil, # -d, --data <data>
|
145
|
-
# 'cert' => nil, # --cacert
|
146
|
-
# 'follow' => false, # -L, --location
|
147
|
-
# 'username' => nil, # -u, --user <user:password>
|
148
|
-
# 'password' => nil,
|
149
|
-
# 'use_ssl' => false, # -k
|
150
|
-
# }
|
151
|
-
|
152
|
-
|
153
|
-
class << self
|
154
|
-
@@http_cfg = {}
|
155
|
-
@@response = nil
|
156
|
-
@@request = nil
|
157
|
-
@@modules = []
|
158
|
-
|
159
|
-
def curl name, secure: false, &block
|
160
|
-
req = {
|
161
|
-
'use_ssl' => secure,
|
162
|
-
}
|
163
|
-
|
164
|
-
if @@http_cfg.key? name
|
165
|
-
req.merge! @@http_cfg[name]
|
166
|
-
raise "No `base_url' set for HTTP client '#{name}'. Check your HTTP config in your environment." unless req['base_url']
|
167
|
-
else
|
168
|
-
req['base_url'] = name
|
169
|
-
end
|
170
|
-
|
171
|
-
SpectreHttpRequest.new(req)._evaluate(&block) if block_given?
|
172
|
-
|
173
|
-
invoke(req)
|
174
|
-
end
|
175
|
-
|
176
|
-
def curl_request
|
177
|
-
raise 'No request has been invoked yet' unless @@request
|
178
|
-
|
179
|
-
@@request
|
180
|
-
end
|
181
|
-
|
182
|
-
def curl_response
|
183
|
-
raise 'There is no response. No request has been invoked yet.' unless @@response
|
184
|
-
|
185
|
-
@@response
|
186
|
-
end
|
187
|
-
|
188
|
-
def register mod
|
189
|
-
raise 'Module must not be nil' unless mod
|
190
|
-
|
191
|
-
@@modules << mod
|
192
|
-
end
|
193
|
-
|
194
|
-
private
|
195
|
-
|
196
|
-
def try_format_json str, pretty: false
|
197
|
-
return str unless str or str.empty?
|
198
|
-
|
199
|
-
begin
|
200
|
-
json = JSON.parse(str)
|
201
|
-
json.obfuscate!(@@secure_keys) unless @@debug
|
202
|
-
|
203
|
-
if pretty
|
204
|
-
str = JSON.pretty_generate(json)
|
205
|
-
else
|
206
|
-
str = JSON.dump(json)
|
207
|
-
end
|
208
|
-
rescue
|
209
|
-
# do nothing
|
210
|
-
end
|
211
|
-
|
212
|
-
str
|
213
|
-
end
|
214
|
-
|
215
|
-
def secure? key
|
216
|
-
@@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
|
217
|
-
end
|
218
|
-
|
219
|
-
def header_to_s headers
|
220
|
-
s = ''
|
221
|
-
|
222
|
-
return s unless headers
|
223
|
-
|
224
|
-
headers.each do |header|
|
225
|
-
key = header[0].to_s
|
226
|
-
value = header[1].to_s
|
227
|
-
value = '*****' if secure?(key) and not @@debug
|
228
|
-
s += "#{key.ljust(30, '.')}: #{value}\n"
|
229
|
-
end
|
230
|
-
|
231
|
-
s
|
232
|
-
end
|
233
|
-
|
234
|
-
def invoke req
|
235
|
-
cmd = [@@curl_path]
|
236
|
-
|
237
|
-
if req['cert'] or req['use_ssl']
|
238
|
-
scheme = 'https'
|
239
|
-
else
|
240
|
-
scheme = 'http'
|
241
|
-
end
|
242
|
-
|
243
|
-
uri = req['base_url']
|
244
|
-
|
245
|
-
unless uri.match /http(?:s)?:\/\//
|
246
|
-
uri = scheme + '://' + uri
|
247
|
-
end
|
248
|
-
|
249
|
-
if req['path']
|
250
|
-
uri += '/' unless uri.end_with? '/'
|
251
|
-
uri += req['path']
|
252
|
-
end
|
253
|
-
|
254
|
-
if req['query']
|
255
|
-
uri += '?'
|
256
|
-
uri += req['query']
|
257
|
-
.map { |x| x.join '='}
|
258
|
-
.join('&')
|
259
|
-
end
|
260
|
-
|
261
|
-
cmd.append('"' + uri + '"')
|
262
|
-
cmd.append('-X', req['method']) unless req['method'] == 'GET' or (req['body'] and req['method'] == 'POST')
|
263
|
-
|
264
|
-
# Call all registered modules
|
265
|
-
@@modules.each do |mod|
|
266
|
-
mod.on_req(req, cmd) if mod.respond_to? :on_req
|
267
|
-
end
|
268
|
-
|
269
|
-
# Add headers to curl command
|
270
|
-
req['headers'].each do |header|
|
271
|
-
cmd.append('-H', '"' + header.join(':') + '"')
|
272
|
-
end if req['headers']
|
273
|
-
|
274
|
-
# Add request body
|
275
|
-
if req['body'] != nil and not req['body'].empty?
|
276
|
-
req_body = try_format_json(req['body']).gsub(/"/, '\\"')
|
277
|
-
cmd.append('-d', '"' + req_body + '"')
|
278
|
-
elsif ['POST', 'PUT', 'PATCH'].include? req['method'].upcase
|
279
|
-
cmd.append('-d', '"\n"')
|
280
|
-
end
|
281
|
-
|
282
|
-
# Add certificate path if one if given
|
283
|
-
if req['cert']
|
284
|
-
raise "Certificate '#{req['cert']}' does not exist" unless File.exists? req['cert']
|
285
|
-
|
286
|
-
cmd.append('--cacert', req['cert'])
|
287
|
-
elsif req['use_ssl'] or uri.start_with? 'https'
|
288
|
-
cmd.append('-k')
|
289
|
-
end
|
290
|
-
|
291
|
-
cmd.append('-i')
|
292
|
-
cmd.append('-v')
|
293
|
-
|
294
|
-
@@request = OpenStruct.new(req)
|
295
|
-
|
296
|
-
sys_cmd = cmd.join(' ')
|
297
|
-
|
298
|
-
@@logger.debug(sys_cmd)
|
299
|
-
|
300
|
-
req_id = SecureRandom.uuid()[0..5]
|
301
|
-
|
302
|
-
req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
|
303
|
-
req_log += header_to_s(req['headers'])
|
304
|
-
req_log += try_format_json(req['body'], pretty: true)
|
305
|
-
|
306
|
-
@@logger.info(req_log)
|
307
|
-
|
308
|
-
start_time = Time.now
|
309
|
-
|
310
|
-
stdin, stdout, stderr, wait_thr = Open3.popen3(sys_cmd)
|
311
|
-
|
312
|
-
end_time = Time.now
|
313
|
-
|
314
|
-
output = stdout.gets(nil)
|
315
|
-
stdout.close
|
316
|
-
|
317
|
-
debug_log = stderr.gets(nil)
|
318
|
-
stderr.close
|
319
|
-
|
320
|
-
# debug_log.lines.each { |x| @@logger.debug x unless x.empty? }
|
321
|
-
|
322
|
-
raise "Unable to request #{uri}. Please check if this URL is correctly configured and reachable." unless output
|
323
|
-
|
324
|
-
@@logger.debug("[<] #{req_id} stdout:\n#{output}")
|
325
|
-
|
326
|
-
header, body = output.split /\r?\n\r?\n/
|
327
|
-
|
328
|
-
result = header.lines.first
|
329
|
-
|
330
|
-
exit_code = wait_thr.value.exitstatus
|
331
|
-
|
332
|
-
raise Exception.new "An error occured while executing curl:\n#{debug_log.lines.map { |x| not x.empty? }}" unless exit_code == 0
|
333
|
-
|
334
|
-
# Parse protocol, version, status code and status message from response
|
335
|
-
match = /^(?<protocol>[A-Za-z0-9]+)\/(?<version>\d+\.?\d*) (?<code>\d+) (?<message>.*)/.match result
|
336
|
-
|
337
|
-
raise "Unexpected result from curl request:\n#{result}" unless match
|
338
|
-
|
339
|
-
res_headers = header.lines[1..-1]
|
340
|
-
.map { |x| /^(?<key>[A-Za-z0-9-]+):\s*(?<value>.*)$/.match x }
|
341
|
-
.select { |x| x != nil }
|
342
|
-
.map { |x| [x[:key].downcase, x[:value]] }
|
343
|
-
|
344
|
-
res = {
|
345
|
-
protocol: match[:protocol],
|
346
|
-
version: match[:version],
|
347
|
-
code: match[:code].to_i,
|
348
|
-
message: match[:message],
|
349
|
-
headers: Hash[res_headers],
|
350
|
-
body: body,
|
351
|
-
}
|
352
|
-
|
353
|
-
# Call all registered modules
|
354
|
-
@@modules.each do |mod|
|
355
|
-
mod.on_res(res, output) if mod.respond_to? :on_res
|
356
|
-
end
|
357
|
-
|
358
|
-
res_log = "[<] #{req_id} #{res[:code]} #{res[:message]} (#{end_time - start_time}s)\n"
|
359
|
-
res_headers.each do |header|
|
360
|
-
res_log += "#{header[0].to_s.ljust(30, '.')}: #{header[1].to_s}\n"
|
361
|
-
end
|
362
|
-
|
363
|
-
if res[:body] != nil and not res[:body].empty?
|
364
|
-
res_log += try_format_json(res[:body], pretty: true)
|
365
|
-
end
|
366
|
-
|
367
|
-
@@logger.info res_log
|
368
|
-
|
369
|
-
@@response = SpectreHttpResponse.new(res)
|
370
|
-
|
371
|
-
raise "Response did not indicate success: #{@@response.code} #{@@response.message}" if req['ensure_success'] and not @@response.success?
|
372
|
-
|
373
|
-
@@response
|
374
|
-
end
|
375
|
-
end
|
376
|
-
|
377
|
-
Spectre.register do |config|
|
378
|
-
@@debug = config['debug']
|
379
|
-
|
380
|
-
@@logger = ::Logger.new(config['log_file'], progname: 'spectre/curl')
|
381
|
-
@@logger.level = @@debug ? Logger::DEBUG : Logger::INFO
|
382
|
-
|
383
|
-
@@secure_keys = config['secure_keys'] || []
|
384
|
-
|
385
|
-
@@curl_path = config['curl_path'] || 'curl'
|
386
|
-
|
387
|
-
if config.key? 'http'
|
388
|
-
@@http_cfg = {}
|
389
|
-
|
390
|
-
config['http'].each do |name, cfg|
|
391
|
-
@@http_cfg[name] = cfg
|
392
|
-
end
|
393
|
-
end
|
394
|
-
end
|
395
|
-
|
396
|
-
Spectre.delegate :curl, :curl_response, :curl_request, to: self
|
397
|
-
end
|
1
|
+
require_relative '../spectre'
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
|
7
|
+
module Spectre::Curl
|
8
|
+
class SpectreHttpRequest < Spectre::DslClass
|
9
|
+
def initialize request
|
10
|
+
@__req = request
|
11
|
+
end
|
12
|
+
|
13
|
+
def method method_name
|
14
|
+
@__req['method'] = method_name.upcase
|
15
|
+
end
|
16
|
+
|
17
|
+
def url base_url
|
18
|
+
@__req['base_url'] = base_url
|
19
|
+
end
|
20
|
+
|
21
|
+
def path url_path
|
22
|
+
@__req['path'] = url_path
|
23
|
+
end
|
24
|
+
|
25
|
+
def header name, value
|
26
|
+
@__req['headers'] = [] unless @__req['headers']
|
27
|
+
@__req['headers'].append [name, value.to_s.strip]
|
28
|
+
end
|
29
|
+
|
30
|
+
def param name, value
|
31
|
+
@__req['query'] = [] unless @__req['query']
|
32
|
+
@__req['query'].append [name, value.to_s.strip]
|
33
|
+
end
|
34
|
+
|
35
|
+
def content_type media_type
|
36
|
+
@__req['headers'] = [] unless @__req['headers']
|
37
|
+
@__req['headers'].append ['Content-Type', media_type]
|
38
|
+
end
|
39
|
+
|
40
|
+
def json data
|
41
|
+
body JSON.pretty_generate(data)
|
42
|
+
content_type 'application/json'
|
43
|
+
end
|
44
|
+
|
45
|
+
def body body_content
|
46
|
+
@__req['body'] = body_content
|
47
|
+
end
|
48
|
+
|
49
|
+
def ensure_success!
|
50
|
+
@__req['ensure_success'] = true
|
51
|
+
end
|
52
|
+
|
53
|
+
def ensure_success?
|
54
|
+
@__req['ensure_success']
|
55
|
+
end
|
56
|
+
|
57
|
+
def authenticate method
|
58
|
+
@__req['auth'] = method
|
59
|
+
end
|
60
|
+
|
61
|
+
def certificate path
|
62
|
+
@__req['cert'] = path
|
63
|
+
use_ssl!
|
64
|
+
end
|
65
|
+
|
66
|
+
def use_ssl!
|
67
|
+
@__req['use_ssl'] = true
|
68
|
+
end
|
69
|
+
|
70
|
+
alias_method :auth, :authenticate
|
71
|
+
alias_method :cert, :certificate
|
72
|
+
alias_method :media_type, :content_type
|
73
|
+
end
|
74
|
+
|
75
|
+
class SpectreHttpHeader
|
76
|
+
def initialize headers
|
77
|
+
@headers = headers || {}
|
78
|
+
end
|
79
|
+
|
80
|
+
def [] key
|
81
|
+
@headers[key.downcase]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class SpectreHttpResponse
|
86
|
+
def initialize res
|
87
|
+
@res = res
|
88
|
+
@data = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def code
|
92
|
+
@res[:code]
|
93
|
+
end
|
94
|
+
|
95
|
+
def message
|
96
|
+
@res[:message]
|
97
|
+
end
|
98
|
+
|
99
|
+
def protocol
|
100
|
+
@res[:protocol]
|
101
|
+
end
|
102
|
+
|
103
|
+
def version
|
104
|
+
@res[:version]
|
105
|
+
end
|
106
|
+
|
107
|
+
def headers
|
108
|
+
SpectreHttpHeader.new @res[:headers]
|
109
|
+
end
|
110
|
+
|
111
|
+
def body
|
112
|
+
@res[:body]
|
113
|
+
end
|
114
|
+
|
115
|
+
def json
|
116
|
+
return nil unless @res[:body]
|
117
|
+
|
118
|
+
if @data == nil
|
119
|
+
begin
|
120
|
+
@data = JSON.parse(@res[:body], object_class: OpenStruct)
|
121
|
+
rescue
|
122
|
+
raise 'invalid json'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
@data
|
127
|
+
end
|
128
|
+
|
129
|
+
def success?
|
130
|
+
@res[:code] < 400
|
131
|
+
end
|
132
|
+
|
133
|
+
def pretty
|
134
|
+
@res.pretty
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# DEFAULT_HTTP_REQUEST = {
|
139
|
+
# 'method' => 'GET', # -X, --request <cmd>
|
140
|
+
# 'base_url' => nil,
|
141
|
+
# 'path' => nil,
|
142
|
+
# 'headers' => nil, # -H, --header <header/@file>
|
143
|
+
# 'query' => nil,
|
144
|
+
# 'body' => nil, # -d, --data <data>
|
145
|
+
# 'cert' => nil, # --cacert
|
146
|
+
# 'follow' => false, # -L, --location
|
147
|
+
# 'username' => nil, # -u, --user <user:password>
|
148
|
+
# 'password' => nil,
|
149
|
+
# 'use_ssl' => false, # -k
|
150
|
+
# }
|
151
|
+
|
152
|
+
|
153
|
+
class << self
|
154
|
+
@@http_cfg = {}
|
155
|
+
@@response = nil
|
156
|
+
@@request = nil
|
157
|
+
@@modules = []
|
158
|
+
|
159
|
+
def curl name, secure: false, &block
|
160
|
+
req = {
|
161
|
+
'use_ssl' => secure,
|
162
|
+
}
|
163
|
+
|
164
|
+
if @@http_cfg.key? name
|
165
|
+
req.merge! @@http_cfg[name]
|
166
|
+
raise "No `base_url' set for HTTP client '#{name}'. Check your HTTP config in your environment." unless req['base_url']
|
167
|
+
else
|
168
|
+
req['base_url'] = name
|
169
|
+
end
|
170
|
+
|
171
|
+
SpectreHttpRequest.new(req)._evaluate(&block) if block_given?
|
172
|
+
|
173
|
+
invoke(req)
|
174
|
+
end
|
175
|
+
|
176
|
+
def curl_request
|
177
|
+
raise 'No request has been invoked yet' unless @@request
|
178
|
+
|
179
|
+
@@request
|
180
|
+
end
|
181
|
+
|
182
|
+
def curl_response
|
183
|
+
raise 'There is no response. No request has been invoked yet.' unless @@response
|
184
|
+
|
185
|
+
@@response
|
186
|
+
end
|
187
|
+
|
188
|
+
def register mod
|
189
|
+
raise 'Module must not be nil' unless mod
|
190
|
+
|
191
|
+
@@modules << mod
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
def try_format_json str, pretty: false
|
197
|
+
return str unless str or str.empty?
|
198
|
+
|
199
|
+
begin
|
200
|
+
json = JSON.parse(str)
|
201
|
+
json.obfuscate!(@@secure_keys) unless @@debug
|
202
|
+
|
203
|
+
if pretty
|
204
|
+
str = JSON.pretty_generate(json)
|
205
|
+
else
|
206
|
+
str = JSON.dump(json)
|
207
|
+
end
|
208
|
+
rescue
|
209
|
+
# do nothing
|
210
|
+
end
|
211
|
+
|
212
|
+
str
|
213
|
+
end
|
214
|
+
|
215
|
+
def secure? key
|
216
|
+
@@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
|
217
|
+
end
|
218
|
+
|
219
|
+
def header_to_s headers
|
220
|
+
s = ''
|
221
|
+
|
222
|
+
return s unless headers
|
223
|
+
|
224
|
+
headers.each do |header|
|
225
|
+
key = header[0].to_s
|
226
|
+
value = header[1].to_s
|
227
|
+
value = '*****' if secure?(key) and not @@debug
|
228
|
+
s += "#{key.ljust(30, '.')}: #{value}\n"
|
229
|
+
end
|
230
|
+
|
231
|
+
s
|
232
|
+
end
|
233
|
+
|
234
|
+
def invoke req
|
235
|
+
cmd = [@@curl_path]
|
236
|
+
|
237
|
+
if req['cert'] or req['use_ssl']
|
238
|
+
scheme = 'https'
|
239
|
+
else
|
240
|
+
scheme = 'http'
|
241
|
+
end
|
242
|
+
|
243
|
+
uri = req['base_url']
|
244
|
+
|
245
|
+
unless uri.match /http(?:s)?:\/\//
|
246
|
+
uri = scheme + '://' + uri
|
247
|
+
end
|
248
|
+
|
249
|
+
if req['path']
|
250
|
+
uri += '/' unless uri.end_with? '/'
|
251
|
+
uri += req['path']
|
252
|
+
end
|
253
|
+
|
254
|
+
if req['query']
|
255
|
+
uri += '?'
|
256
|
+
uri += req['query']
|
257
|
+
.map { |x| x.join '='}
|
258
|
+
.join('&')
|
259
|
+
end
|
260
|
+
|
261
|
+
cmd.append('"' + uri + '"')
|
262
|
+
cmd.append('-X', req['method']) unless req['method'] == 'GET' or (req['body'] and req['method'] == 'POST')
|
263
|
+
|
264
|
+
# Call all registered modules
|
265
|
+
@@modules.each do |mod|
|
266
|
+
mod.on_req(req, cmd) if mod.respond_to? :on_req
|
267
|
+
end
|
268
|
+
|
269
|
+
# Add headers to curl command
|
270
|
+
req['headers'].each do |header|
|
271
|
+
cmd.append('-H', '"' + header.join(':') + '"')
|
272
|
+
end if req['headers']
|
273
|
+
|
274
|
+
# Add request body
|
275
|
+
if req['body'] != nil and not req['body'].empty?
|
276
|
+
req_body = try_format_json(req['body']).gsub(/"/, '\\"')
|
277
|
+
cmd.append('-d', '"' + req_body + '"')
|
278
|
+
elsif ['POST', 'PUT', 'PATCH'].include? req['method'].upcase
|
279
|
+
cmd.append('-d', '"\n"')
|
280
|
+
end
|
281
|
+
|
282
|
+
# Add certificate path if one if given
|
283
|
+
if req['cert']
|
284
|
+
raise "Certificate '#{req['cert']}' does not exist" unless File.exists? req['cert']
|
285
|
+
|
286
|
+
cmd.append('--cacert', req['cert'])
|
287
|
+
elsif req['use_ssl'] or uri.start_with? 'https'
|
288
|
+
cmd.append('-k')
|
289
|
+
end
|
290
|
+
|
291
|
+
cmd.append('-i')
|
292
|
+
cmd.append('-v')
|
293
|
+
|
294
|
+
@@request = OpenStruct.new(req)
|
295
|
+
|
296
|
+
sys_cmd = cmd.join(' ')
|
297
|
+
|
298
|
+
@@logger.debug(sys_cmd)
|
299
|
+
|
300
|
+
req_id = SecureRandom.uuid()[0..5]
|
301
|
+
|
302
|
+
req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
|
303
|
+
req_log += header_to_s(req['headers'])
|
304
|
+
req_log += try_format_json(req['body'], pretty: true)
|
305
|
+
|
306
|
+
@@logger.info(req_log)
|
307
|
+
|
308
|
+
start_time = Time.now
|
309
|
+
|
310
|
+
stdin, stdout, stderr, wait_thr = Open3.popen3(sys_cmd)
|
311
|
+
|
312
|
+
end_time = Time.now
|
313
|
+
|
314
|
+
output = stdout.gets(nil)
|
315
|
+
stdout.close
|
316
|
+
|
317
|
+
debug_log = stderr.gets(nil)
|
318
|
+
stderr.close
|
319
|
+
|
320
|
+
# debug_log.lines.each { |x| @@logger.debug x unless x.empty? }
|
321
|
+
|
322
|
+
raise "Unable to request #{uri}. Please check if this URL is correctly configured and reachable." unless output
|
323
|
+
|
324
|
+
@@logger.debug("[<] #{req_id} stdout:\n#{output}")
|
325
|
+
|
326
|
+
header, body = output.split /\r?\n\r?\n/
|
327
|
+
|
328
|
+
result = header.lines.first
|
329
|
+
|
330
|
+
exit_code = wait_thr.value.exitstatus
|
331
|
+
|
332
|
+
raise Exception.new "An error occured while executing curl:\n#{debug_log.lines.map { |x| not x.empty? }}" unless exit_code == 0
|
333
|
+
|
334
|
+
# Parse protocol, version, status code and status message from response
|
335
|
+
match = /^(?<protocol>[A-Za-z0-9]+)\/(?<version>\d+\.?\d*) (?<code>\d+) (?<message>.*)/.match result
|
336
|
+
|
337
|
+
raise "Unexpected result from curl request:\n#{result}" unless match
|
338
|
+
|
339
|
+
res_headers = header.lines[1..-1]
|
340
|
+
.map { |x| /^(?<key>[A-Za-z0-9-]+):\s*(?<value>.*)$/.match x }
|
341
|
+
.select { |x| x != nil }
|
342
|
+
.map { |x| [x[:key].downcase, x[:value]] }
|
343
|
+
|
344
|
+
res = {
|
345
|
+
protocol: match[:protocol],
|
346
|
+
version: match[:version],
|
347
|
+
code: match[:code].to_i,
|
348
|
+
message: match[:message],
|
349
|
+
headers: Hash[res_headers],
|
350
|
+
body: body,
|
351
|
+
}
|
352
|
+
|
353
|
+
# Call all registered modules
|
354
|
+
@@modules.each do |mod|
|
355
|
+
mod.on_res(res, output) if mod.respond_to? :on_res
|
356
|
+
end
|
357
|
+
|
358
|
+
res_log = "[<] #{req_id} #{res[:code]} #{res[:message]} (#{end_time - start_time}s)\n"
|
359
|
+
res_headers.each do |header|
|
360
|
+
res_log += "#{header[0].to_s.ljust(30, '.')}: #{header[1].to_s}\n"
|
361
|
+
end
|
362
|
+
|
363
|
+
if res[:body] != nil and not res[:body].empty?
|
364
|
+
res_log += try_format_json(res[:body], pretty: true)
|
365
|
+
end
|
366
|
+
|
367
|
+
@@logger.info res_log
|
368
|
+
|
369
|
+
@@response = SpectreHttpResponse.new(res)
|
370
|
+
|
371
|
+
raise "Response did not indicate success: #{@@response.code} #{@@response.message}" if req['ensure_success'] and not @@response.success?
|
372
|
+
|
373
|
+
@@response
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
Spectre.register do |config|
|
378
|
+
@@debug = config['debug']
|
379
|
+
|
380
|
+
@@logger = ::Logger.new(config['log_file'], progname: 'spectre/curl')
|
381
|
+
@@logger.level = @@debug ? Logger::DEBUG : Logger::INFO
|
382
|
+
|
383
|
+
@@secure_keys = config['secure_keys'] || []
|
384
|
+
|
385
|
+
@@curl_path = config['curl_path'] || 'curl'
|
386
|
+
|
387
|
+
if config.key? 'http'
|
388
|
+
@@http_cfg = {}
|
389
|
+
|
390
|
+
config['http'].each do |name, cfg|
|
391
|
+
@@http_cfg[name] = cfg
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
Spectre.delegate :curl, :curl_response, :curl_request, to: self
|
397
|
+
end
|