spectre-core 1.12.0 → 1.12.3

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.
data/lib/spectre/http.rb CHANGED
@@ -1,364 +1,379 @@
1
- require_relative '../spectre'
2
-
3
- require 'net/http'
4
- require 'openssl'
5
- require 'json'
6
- require 'securerandom'
7
- require 'logger'
8
- require 'ostruct'
9
-
10
- module Spectre
11
- module Http
12
- DEFAULT_HTTP_CONFIG = {
13
- 'method' => 'GET',
14
- 'path' => '',
15
- 'host' => nil,
16
- 'port' => 80,
17
- 'scheme' => 'http',
18
- 'use_ssl' => false,
19
- 'cert' => nil,
20
- 'headers' => nil,
21
- 'query' => nil,
22
- 'content_type' => '',
23
- }.freeze
24
-
25
- @@modules = []
26
-
27
- class SpectreHttpRequest < Spectre::DslClass
28
- class Headers
29
- CONTENT_TYPE = 'Content-Type'
30
- UNIQUE_HEADERS = [CONTENT_TYPE].freeze
31
- end
32
-
33
- def initialize request
34
- @__req = request
35
- end
36
-
37
- def method method_name
38
- @__req['method'] = method_name.upcase
39
- end
40
-
41
- def url base_url
42
- @__req['base_url'] = base_url
43
- end
44
-
45
- def path url_path
46
- @__req['path'] = url_path
47
- end
48
-
49
- def timeout seconds
50
- @__req['timeout'] = seconds
51
- end
52
-
53
- def header name, value
54
- @__req['headers'] ||= []
55
- @__req['headers'].append [name, value.to_s.strip]
56
- end
57
-
58
- def param name, value
59
- @__req['query'] ||= []
60
- @__req['query'].append [name, value.to_s.strip]
61
- end
62
-
63
- def content_type media_type
64
- @__req['content_type'] = media_type
65
- end
66
-
67
- def json data
68
- data = data.to_h if data.is_a? OpenStruct
69
- body JSON.pretty_generate(data)
70
-
71
- # TODO: Only set content type, if not explicitly set
72
- content_type('application/json')
73
- end
74
-
75
- def body body_content
76
- @__req['body'] = body_content.to_s
77
- end
78
-
79
- def ensure_success!
80
- @__req['ensure_success'] = true
81
- end
82
-
83
- def ensure_success?
84
- @__req['ensure_success']
85
- end
86
-
87
- def authenticate method
88
- @__req['auth'] = method
89
- end
90
-
91
- def no_auth!
92
- @__req['auth'] = 'none'
93
- end
94
-
95
- def certificate path
96
- @__req['cert'] = path
97
- end
98
-
99
- def use_ssl!
100
- @__req['use_ssl'] = true
101
- end
102
-
103
- def to_s
104
- @__req.to_s
105
- end
106
-
107
- alias_method :auth, :authenticate
108
- alias_method :cert, :certificate
109
- alias_method :media_type, :content_type
110
- end
111
-
112
- class SpectreHttpHeader
113
- def initialize headers
114
- @headers = headers || {}
115
- end
116
-
117
- def [] key
118
- return nil unless @headers.key?(key.downcase)
119
-
120
- @headers[key.downcase].first
121
- end
122
-
123
- def to_s
124
- @headers.to_s
125
- end
126
- end
127
-
128
- class SpectreHttpResponse
129
- attr_reader :code, :message, :headers, :body
130
-
131
- def initialize net_res
132
- @code = net_res.code.to_i
133
- @message = net_res.message
134
- @body = net_res.body
135
- @headers = SpectreHttpHeader.new(net_res.to_hash)
136
- @json_data = nil
137
- end
138
-
139
- def json
140
- if !@body.nil? and @json_data.nil?
141
- begin
142
- @json_data = JSON.parse(@body, object_class: OpenStruct)
143
- rescue JSON::ParserError
144
- raise "Body content is not a valid JSON:\n#{@body}"
145
- end
146
- end
147
-
148
- @json_data
149
- end
150
-
151
- def success?
152
- @code < 400
153
- end
154
- end
155
-
156
-
157
- class << self
158
- @@http_cfg = {}
159
- @@response = nil
160
- @@request = nil
161
- @@modules = []
162
- @@secure_keys = []
163
-
164
- def https name, &block
165
- http(name, secure: true, &block)
166
- end
167
-
168
- def http name, secure: false, &block
169
- req = {}
170
-
171
- if @@http_cfg.key? name
172
- req.deep_merge! @@http_cfg[name].deep_clone
173
- raise "No `base_url' set for HTTP client '#{name}'. Check your HTTP config in your environment." unless req['base_url']
174
- else
175
- req['base_url'] = name
176
- end
177
-
178
- req['use_ssl'] = secure unless secure.nil?
179
-
180
- SpectreHttpRequest.new(req)._evaluate(&block) if block_given?
181
-
182
- invoke(req)
183
- end
184
-
185
- def request
186
- raise 'No request has been invoked yet' unless @@request
187
-
188
- @@request
189
- end
190
-
191
- def response
192
- raise 'There is no response. No request has been invoked yet.' unless @@response
193
-
194
- @@response
195
- end
196
-
197
- def register mod
198
- raise 'Module must not be nil' unless mod
199
-
200
- @@modules << mod
201
- end
202
-
203
- private
204
-
205
- def try_format_json str, pretty: false
206
- return str unless str or str.empty?
207
-
208
- begin
209
- json = JSON.parse(str)
210
- json.obfuscate!(@@secure_keys) unless @@debug
211
-
212
- if pretty
213
- str = JSON.pretty_generate(json)
214
- else
215
- str = JSON.dump(json)
216
- end
217
- rescue
218
- # do nothing
219
- end
220
-
221
- str
222
- end
223
-
224
- def secure? key
225
- @@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
226
- end
227
-
228
- def header_to_s headers
229
- s = ''
230
- headers.each_header.each do |header, value|
231
- value = '*****' if secure?(header) and not @@debug
232
- s += "#{header.to_s.ljust(30, '.')}: #{value.to_s}\n"
233
- end
234
- s
235
- end
236
-
237
- def invoke req
238
- @@request = nil
239
-
240
- # Build URI
241
-
242
- scheme = req['use_ssl'] ? 'https' : 'http'
243
- base_url = req['base_url']
244
-
245
- unless base_url.match /http(?:s)?:\/\//
246
- base_url = scheme + '://' + base_url
247
- end
248
-
249
- if req['path']
250
- base_url = base_url + '/' unless base_url.end_with? '/'
251
- base_url += req['path']
252
- end
253
-
254
- uri = URI(base_url)
255
-
256
- raise "'#{uri}' is not a valid uri" unless uri.host
257
-
258
- # Build query parameters
259
-
260
- uri.query = URI.encode_www_form(req['query']) unless not req['query'] or req['query'].empty?
261
-
262
- # Create HTTP client
263
-
264
- net_http = Net::HTTP.new(uri.host, uri.port)
265
- net_http.read_timeout = req['timeout'] || 180
266
-
267
- if uri.scheme == 'https'
268
- net_http.use_ssl = true
269
-
270
- if req.key? 'cert'
271
- raise "Certificate '#{req['cert']}' does not exist" unless File.exists? req['cert']
272
-
273
- net_http.verify_mode = OpenSSL::SSL::VERIFY_PEER
274
- net_http.ca_file = req['cert']
275
- else
276
- net_http.verify_mode = OpenSSL::SSL::VERIFY_NONE
277
- end
278
- end
279
-
280
- # Create HTTP Request
281
-
282
- net_req = Net::HTTPGenericRequest.new(req['method'], true, true, uri)
283
- net_req.body = req['body']
284
- net_req.content_type = req['content_type'] if req['content_type'] and not req['content_type'].empty?
285
-
286
- if req['headers']
287
- req['headers'].each do |header|
288
- net_req[header[0]] = header[1]
289
- end
290
- end
291
-
292
- req_id = SecureRandom.uuid()[0..5]
293
-
294
- # Run HTTP modules
295
-
296
- @@modules.each do |mod|
297
- mod.on_req(net_http, net_req, req) if mod.respond_to? :on_req
298
- end
299
-
300
- # Log request
301
-
302
- req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
303
- req_log += header_to_s(net_req)
304
- req_log += try_format_json(req['body'], pretty: true) if req['body'] != nil and not req['body'].empty?
305
-
306
- @@logger.info(req_log)
307
-
308
- # Request
309
-
310
- start_time = Time.now
311
-
312
- begin
313
- net_res = net_http.request(net_req)
314
- rescue SocketError => e
315
- raise "The request '#{req['method']} #{uri}' failed. Please check if the given URL '#{uri}' is valid and available or a corresponding HTTP config in the environment file exists. See log for more details. Original.\nOriginal error was: #{e.message}"
316
- end
317
-
318
- end_time = Time.now
319
-
320
- req['started_at'] = start_time
321
- req['finished_at'] = end_time
322
-
323
- # Run HTTP modules
324
-
325
- @@modules.each do |mod|
326
- mod.on_res(net_http, net_res, req) if mod.respond_to? :on_res
327
- end
328
-
329
- # Log response
330
-
331
- res_log = "[<] #{req_id} #{net_res.code} #{net_res.message} (#{end_time - start_time}s)\n"
332
- res_log += header_to_s(net_res)
333
- res_log += try_format_json(net_res.body, pretty: true) unless net_res.body.nil? or net_res.body.empty?
334
-
335
- @@logger.info(res_log)
336
-
337
- fail "Response code of #{req_id} did not indicate success: #{net_res.code} #{net_res.message}" if req['ensure_success'] and net_res.code.to_i >= 400
338
-
339
- # Set global request and response variables
340
-
341
- @@request = OpenStruct.new(req)
342
- @@request.freeze
343
-
344
- @@response = SpectreHttpResponse.new(net_res)
345
- end
346
- end
347
-
348
- Spectre.register do |config|
349
- @@logger = ::Logger.new(config['log_file'], progname: 'spectre/http')
350
- @@secure_keys = config['secure_keys'] || []
351
- @@debug = config['debug']
352
-
353
- if config.key? 'http'
354
- @@http_cfg = {}
355
-
356
- config['http'].each do |name, cfg|
357
- @@http_cfg[name] = cfg
358
- end
359
- end
360
- end
361
-
362
- Spectre.delegate :http, :https, :request, :response, to: self
363
- end
364
- end
1
+ require_relative '../spectre'
2
+
3
+ require 'net/http'
4
+ require 'openssl'
5
+ require 'json'
6
+ require 'securerandom'
7
+ require 'logger'
8
+ require 'ostruct'
9
+
10
+ module Spectre
11
+ module Http
12
+ DEFAULT_HTTP_CONFIG = {
13
+ 'method' => 'GET',
14
+ 'path' => '',
15
+ 'host' => nil,
16
+ 'port' => 80,
17
+ 'scheme' => 'http',
18
+ 'use_ssl' => false,
19
+ 'cert' => nil,
20
+ 'headers' => nil,
21
+ 'query' => nil,
22
+ 'content_type' => '',
23
+ 'timeout' => 180,
24
+ 'retries' => 0,
25
+ }
26
+
27
+ @@modules = []
28
+
29
+ class HttpError < Exception
30
+ def initialize message
31
+ super message
32
+ end
33
+ end
34
+
35
+ class SpectreHttpRequest < Spectre::DslClass
36
+ class Headers
37
+ CONTENT_TYPE = 'Content-Type'
38
+ UNIQUE_HEADERS = [CONTENT_TYPE].freeze
39
+ end
40
+
41
+ def initialize request
42
+ @__req = request
43
+ end
44
+
45
+ def method method_name
46
+ @__req['method'] = method_name.upcase
47
+ end
48
+
49
+ def url base_url
50
+ @__req['base_url'] = base_url
51
+ end
52
+
53
+ def path url_path
54
+ @__req['path'] = url_path
55
+ end
56
+
57
+ def timeout seconds
58
+ @__req['timeout'] = seconds
59
+ end
60
+
61
+ def retries count
62
+ @__req['retries'] = count
63
+ end
64
+
65
+ def header name, value
66
+ @__req['headers'] ||= []
67
+ @__req['headers'].append [name, value.to_s.strip]
68
+ end
69
+
70
+ def param name, value
71
+ @__req['query'] ||= []
72
+ @__req['query'].append [name, value.to_s.strip]
73
+ end
74
+
75
+ def content_type media_type
76
+ @__req['content_type'] = media_type
77
+ end
78
+
79
+ def json data
80
+ data = data.to_h if data.is_a? OpenStruct
81
+ body JSON.pretty_generate(data)
82
+
83
+ # TODO: Only set content type, if not explicitly set
84
+ content_type('application/json')
85
+ end
86
+
87
+ def body body_content
88
+ @__req['body'] = body_content.to_s
89
+ end
90
+
91
+ def ensure_success!
92
+ @__req['ensure_success'] = true
93
+ end
94
+
95
+ def ensure_success?
96
+ @__req['ensure_success']
97
+ end
98
+
99
+ def authenticate method
100
+ @__req['auth'] = method
101
+ end
102
+
103
+ def no_auth!
104
+ @__req['auth'] = 'none'
105
+ end
106
+
107
+ def certificate path
108
+ @__req['cert'] = path
109
+ end
110
+
111
+ def use_ssl!
112
+ @__req['use_ssl'] = true
113
+ end
114
+
115
+ def to_s
116
+ @__req.to_s
117
+ end
118
+
119
+ alias_method :auth, :authenticate
120
+ alias_method :cert, :certificate
121
+ alias_method :media_type, :content_type
122
+ end
123
+
124
+ class SpectreHttpHeader
125
+ def initialize headers
126
+ @headers = headers || {}
127
+ end
128
+
129
+ def [] key
130
+ return nil unless @headers.key?(key.downcase)
131
+
132
+ @headers[key.downcase].first
133
+ end
134
+
135
+ def to_s
136
+ @headers.to_s
137
+ end
138
+ end
139
+
140
+ class SpectreHttpResponse
141
+ attr_reader :code, :message, :headers, :body
142
+
143
+ def initialize net_res
144
+ @code = net_res.code.to_i
145
+ @message = net_res.message
146
+ @body = net_res.body
147
+ @headers = SpectreHttpHeader.new(net_res.to_hash)
148
+ @json_data = nil
149
+ end
150
+
151
+ def json
152
+ if !@body.nil? and @json_data.nil?
153
+ begin
154
+ @json_data = JSON.parse(@body, object_class: OpenStruct)
155
+ rescue JSON::ParserError
156
+ raise HttpError.new("Body content is not a valid JSON:\n#{@body}")
157
+ end
158
+ end
159
+
160
+ @json_data
161
+ end
162
+
163
+ def success?
164
+ @code < 400
165
+ end
166
+ end
167
+
168
+
169
+ class << self
170
+ @@http_cfg = {}
171
+ @@response = nil
172
+ @@request = nil
173
+ @@modules = []
174
+ @@secure_keys = []
175
+
176
+ def https name, &block
177
+ http(name, secure: true, &block)
178
+ end
179
+
180
+ def http name, secure: false, &block
181
+ req = DEFAULT_HTTP_CONFIG.clone
182
+
183
+ if @@http_cfg.key? name
184
+ req.deep_merge! @@http_cfg[name].deep_clone
185
+ raise HttpError.new("No `base_url' set for HTTP client '#{name}'. Check your HTTP config in your environment.") unless req['base_url']
186
+ else
187
+ req['base_url'] = name
188
+ end
189
+
190
+ req['use_ssl'] = secure unless secure.nil?
191
+
192
+ SpectreHttpRequest.new(req)._evaluate(&block) if block_given?
193
+
194
+ invoke(req)
195
+ end
196
+
197
+ def request
198
+ raise 'No request has been invoked yet' unless @@request
199
+
200
+ @@request
201
+ end
202
+
203
+ def response
204
+ raise 'There is no response. No request has been invoked yet.' unless @@response
205
+
206
+ @@response
207
+ end
208
+
209
+ def register mod
210
+ raise 'Module must not be nil' unless mod
211
+
212
+ @@modules << mod
213
+ end
214
+
215
+ private
216
+
217
+ def try_format_json str, pretty: false
218
+ return str unless str or str.empty?
219
+
220
+ begin
221
+ json = JSON.parse(str)
222
+ json.obfuscate!(@@secure_keys) unless @@debug
223
+
224
+ if pretty
225
+ str = JSON.pretty_generate(json)
226
+ else
227
+ str = JSON.dump(json)
228
+ end
229
+ rescue
230
+ # do nothing
231
+ end
232
+
233
+ str
234
+ end
235
+
236
+ def secure? key
237
+ @@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
238
+ end
239
+
240
+ def header_to_s headers
241
+ s = ''
242
+ headers.each_header.each do |header, value|
243
+ value = '*****' if secure?(header) and not @@debug
244
+ s += "#{header.to_s.ljust(30, '.')}: #{value.to_s}\n"
245
+ end
246
+ s
247
+ end
248
+
249
+ def invoke req
250
+ @@request = nil
251
+
252
+ # Build URI
253
+
254
+ scheme = req['use_ssl'] ? 'https' : 'http'
255
+ base_url = req['base_url']
256
+
257
+ unless base_url.match /http(?:s)?:\/\//
258
+ base_url = scheme + '://' + base_url
259
+ end
260
+
261
+ if req['path']
262
+ base_url = base_url + '/' unless base_url.end_with? '/'
263
+ base_url += req['path']
264
+ end
265
+
266
+ uri = URI(base_url)
267
+
268
+ raise HttpError.new("'#{uri}' is not a valid uri") unless uri.host
269
+
270
+ # Build query parameters
271
+
272
+ uri.query = URI.encode_www_form(req['query']) unless not req['query'] or req['query'].empty?
273
+
274
+ # Create HTTP client
275
+
276
+ net_http = Net::HTTP.new(uri.host, uri.port)
277
+ net_http.read_timeout = req['timeout']
278
+ net_http.max_retries = req['retries']
279
+
280
+ if uri.scheme == 'https'
281
+ net_http.use_ssl = true
282
+
283
+ if req['cert']
284
+ raise HttpError.new("Certificate '#{req['cert']}' does not exist") unless File.exists? req['cert']
285
+
286
+ net_http.verify_mode = OpenSSL::SSL::VERIFY_PEER
287
+ net_http.ca_file = req['cert']
288
+ else
289
+ net_http.verify_mode = OpenSSL::SSL::VERIFY_NONE
290
+ end
291
+ end
292
+
293
+ # Create HTTP Request
294
+
295
+ net_req = Net::HTTPGenericRequest.new(req['method'], true, true, uri)
296
+ net_req.body = req['body']
297
+ net_req.content_type = req['content_type'] if req['content_type'] and not req['content_type'].empty?
298
+
299
+ if req['headers']
300
+ req['headers'].each do |header|
301
+ net_req[header[0]] = header[1]
302
+ end
303
+ end
304
+
305
+ req_id = SecureRandom.uuid()[0..5]
306
+
307
+ # Run HTTP modules
308
+
309
+ @@modules.each do |mod|
310
+ mod.on_req(net_http, net_req, req) if mod.respond_to? :on_req
311
+ end
312
+
313
+ # Log request
314
+
315
+ req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
316
+ req_log += header_to_s(net_req)
317
+ req_log += try_format_json(req['body'], pretty: true) if req['body'] != nil and not req['body'].empty?
318
+
319
+ @@logger.info(req_log)
320
+
321
+ # Request
322
+
323
+ start_time = Time.now
324
+
325
+ begin
326
+ net_res = net_http.request(net_req)
327
+ rescue SocketError => e
328
+ raise HttpError.new("The request '#{req['method']} #{uri}' failed. Please check if the given URL '#{uri}' is valid and available or a corresponding HTTP config in the environment file exists. See log for more details. Original.\nOriginal error was: #{e.message}")
329
+ rescue Net::ReadTimeout
330
+ raise HttpError.new("HTTP timeout of #{net_http.read_timeout}s exceeded")
331
+ end
332
+
333
+ end_time = Time.now
334
+
335
+ req['started_at'] = start_time
336
+ req['finished_at'] = end_time
337
+
338
+ # Run HTTP modules
339
+
340
+ @@modules.each do |mod|
341
+ mod.on_res(net_http, net_res, req) if mod.respond_to? :on_res
342
+ end
343
+
344
+ # Log response
345
+
346
+ res_log = "[<] #{req_id} #{net_res.code} #{net_res.message} (#{end_time - start_time}s)\n"
347
+ res_log += header_to_s(net_res)
348
+ res_log += try_format_json(net_res.body, pretty: true) unless net_res.body.nil? or net_res.body.empty?
349
+
350
+ @@logger.info(res_log)
351
+
352
+ fail "Response code of #{req_id} did not indicate success: #{net_res.code} #{net_res.message}" if req['ensure_success'] and net_res.code.to_i >= 400
353
+
354
+ # Set global request and response variables
355
+
356
+ @@request = OpenStruct.new(req)
357
+ @@request.freeze
358
+
359
+ @@response = SpectreHttpResponse.new(net_res)
360
+ end
361
+ end
362
+
363
+ Spectre.register do |config|
364
+ @@logger = ::Logger.new(config['log_file'], progname: 'spectre/http')
365
+ @@secure_keys = config['secure_keys'] || []
366
+ @@debug = config['debug']
367
+
368
+ if config.key? 'http'
369
+ @@http_cfg = {}
370
+
371
+ config['http'].each do |name, cfg|
372
+ @@http_cfg[name] = cfg
373
+ end
374
+ end
375
+ end
376
+
377
+ Spectre.delegate :http, :https, :request, :response, to: self
378
+ end
379
+ end