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