mint_http 0.1.8 → 0.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mint_http/config.rb +17 -0
- data/lib/mint_http/request.rb +79 -5
- data/lib/mint_http/request_logger.rb +218 -0
- data/lib/mint_http/response.rb +46 -1
- data/lib/mint_http/version.rb +1 -1
- data/lib/mint_http.rb +18 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0d6eb6fc74d83a79da5384f6ecec034ea2402752539ac29b40b43c85a0422a0
|
4
|
+
data.tar.gz: 29ba4aa2fdd0f1bb3dfeccb44d83766ea1908d945318db6cf918127c39c5eea7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0d28be37857d0943a281ff4469095e0a9cb6d58a67a47a14dd7290970564e39306bde41d772a00511136d7cf0a3fef2a202ea3d300e64c40a44b5bc2783079d
|
7
|
+
data.tar.gz: f0c4050bb642956395b7ccf13279659ef73e23787304127b44c663e9153d48510151438bf4295d8c3e3eeb8e86f80548d0be695daeb8c36829863ae8b532455b
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MintHttp
|
4
|
+
class Config
|
5
|
+
# @!attribute [rw] logger
|
6
|
+
# @return [Logger] logger to be used for logging request details
|
7
|
+
attr_accessor :logger
|
8
|
+
|
9
|
+
# @!attribute [rw] filter_params_list
|
10
|
+
# @return [Array[String|Symbol]] logger to be used for logging request details
|
11
|
+
attr_accessor :filter_params_list
|
12
|
+
|
13
|
+
# @!attribute [rw] filter_params
|
14
|
+
# @return [Boolean] indicate if MintHttp should filter out params from request logs
|
15
|
+
attr_accessor :filter_params
|
16
|
+
end
|
17
|
+
end
|
data/lib/mint_http/request.rb
CHANGED
@@ -23,6 +23,10 @@ module MintHttp
|
|
23
23
|
attr_reader :ssl_verify_mode
|
24
24
|
attr_reader :ssl_verify_hostname
|
25
25
|
|
26
|
+
# Attributes only available when request is made
|
27
|
+
attr_reader :method
|
28
|
+
attr_reader :request_url
|
29
|
+
|
26
30
|
def initialize
|
27
31
|
@pool = nil
|
28
32
|
@base_url = nil
|
@@ -44,6 +48,10 @@ module MintHttp
|
|
44
48
|
@ssl_verify_mode = nil
|
45
49
|
@ssl_verify_hostname = nil
|
46
50
|
|
51
|
+
@logger = MintHttp.config.logger
|
52
|
+
@filter_params_list = MintHttp.config.filter_params_list
|
53
|
+
@filter_params = MintHttp.config.filter_params
|
54
|
+
|
47
55
|
header('User-Agent' => 'Mint Http')
|
48
56
|
as_json
|
49
57
|
end
|
@@ -204,6 +212,28 @@ module MintHttp
|
|
204
212
|
self
|
205
213
|
end
|
206
214
|
|
215
|
+
# @param [Logger] logger
|
216
|
+
def use_logger(logger)
|
217
|
+
@logger = logger
|
218
|
+
self
|
219
|
+
end
|
220
|
+
|
221
|
+
def no_logger
|
222
|
+
@logger = Logger.new('/dev/null')
|
223
|
+
self
|
224
|
+
end
|
225
|
+
|
226
|
+
# @param [Array[String]] params
|
227
|
+
def filter_param(*params)
|
228
|
+
@filter_params_list.concat(params)
|
229
|
+
self
|
230
|
+
end
|
231
|
+
|
232
|
+
def should_filter_params(filter = true)
|
233
|
+
@filter_params = filter
|
234
|
+
self
|
235
|
+
end
|
236
|
+
|
207
237
|
def get(url, params = {})
|
208
238
|
query(params).send_request('get', url)
|
209
239
|
end
|
@@ -233,20 +263,60 @@ module MintHttp
|
|
233
263
|
end
|
234
264
|
|
235
265
|
def send_request(method, url)
|
266
|
+
@method = method
|
267
|
+
|
268
|
+
logger = RequestLogger.new(@logger, @filter_params_list, @filter_params)
|
236
269
|
url, net_request, options = build_request(method, url)
|
237
270
|
|
238
|
-
|
239
|
-
|
271
|
+
logger.log_request(self, net_request)
|
272
|
+
logger.log_start
|
273
|
+
|
274
|
+
begin
|
275
|
+
res = with_client(url.hostname, url.port, options) do |http|
|
276
|
+
logger.log_connected
|
277
|
+
logger.log_connection_info(http)
|
278
|
+
http.request(net_request)
|
279
|
+
end
|
280
|
+
rescue StandardError => error
|
281
|
+
logger.log_end
|
282
|
+
logger.log_error(error)
|
283
|
+
logger.write_log
|
284
|
+
raise error
|
240
285
|
end
|
241
286
|
|
242
|
-
|
287
|
+
logger.log_end
|
288
|
+
|
289
|
+
response = Response.new(res, net_request, self)
|
290
|
+
|
291
|
+
logger.log_response(response)
|
292
|
+
logger.put_timing(response)
|
293
|
+
logger.write_log
|
294
|
+
|
295
|
+
response
|
243
296
|
end
|
244
297
|
|
245
298
|
private
|
246
299
|
|
247
|
-
def
|
300
|
+
def build_url(url)
|
248
301
|
url = URI.parse(url)
|
249
|
-
|
302
|
+
|
303
|
+
unless url.is_a?(URI::Generic)
|
304
|
+
return url
|
305
|
+
end
|
306
|
+
|
307
|
+
unless @base_url
|
308
|
+
return url
|
309
|
+
end
|
310
|
+
|
311
|
+
unless @base_url.path.match?(/\/$/)
|
312
|
+
@base_url.path += '/'
|
313
|
+
end
|
314
|
+
|
315
|
+
@base_url + url.path.gsub(/^\/+/, '')
|
316
|
+
end
|
317
|
+
|
318
|
+
def build_request(method, url)
|
319
|
+
@request_url = url = build_url(url)
|
250
320
|
|
251
321
|
unless %w[http https].include?(url.scheme)
|
252
322
|
raise ArgumentError, "Only HTTP and HTTPS URLs are allowed"
|
@@ -348,5 +418,9 @@ module MintHttp
|
|
348
418
|
def net_factory
|
349
419
|
@net_factory ||= NetHttpFactory.new
|
350
420
|
end
|
421
|
+
|
422
|
+
def clock_time
|
423
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
424
|
+
end
|
351
425
|
end
|
352
426
|
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
module MintHttp
|
6
|
+
class RequestLogger
|
7
|
+
MAX_BODY_SIZE = 15 * 1024
|
8
|
+
BODY_ALLOWED_TYPES = [/application\/.+/, /text\/.+/]
|
9
|
+
|
10
|
+
attr_reader :logger
|
11
|
+
attr_reader :filter_list
|
12
|
+
attr_reader :time_started
|
13
|
+
attr_reader :time_ended
|
14
|
+
attr_reader :time_connected
|
15
|
+
attr_reader :time_total
|
16
|
+
attr_reader :time_connecting
|
17
|
+
attr_reader :tls_config
|
18
|
+
|
19
|
+
# @param [Logger] logger
|
20
|
+
# @param [Array[String|Symbol]] request
|
21
|
+
def initialize(logger, filter_list, filter = true)
|
22
|
+
@logger = logger
|
23
|
+
@filter_list = filter_list.map(&:downcase)
|
24
|
+
@filter = filter
|
25
|
+
@time_started = 0.0
|
26
|
+
@time_ended = 0.0
|
27
|
+
@time_connected = 0.0
|
28
|
+
@time_total = 0.0
|
29
|
+
@time_connecting = 0.0
|
30
|
+
|
31
|
+
@request = nil
|
32
|
+
@net_request = nil
|
33
|
+
@response = nil
|
34
|
+
@error = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def log_request(request, net_request)
|
38
|
+
@request = request
|
39
|
+
@net_request = net_request
|
40
|
+
end
|
41
|
+
|
42
|
+
def log_response(response)
|
43
|
+
@response = response
|
44
|
+
end
|
45
|
+
|
46
|
+
def log_error(error)
|
47
|
+
@error = error
|
48
|
+
end
|
49
|
+
|
50
|
+
def log_start
|
51
|
+
@time_started = clock_time
|
52
|
+
end
|
53
|
+
|
54
|
+
def log_end
|
55
|
+
@time_ended = clock_time
|
56
|
+
@time_total = @time_ended - @time_started
|
57
|
+
end
|
58
|
+
|
59
|
+
def log_connected
|
60
|
+
@time_connected = clock_time
|
61
|
+
@time_connecting = @time_connected - @time_started
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param [MintHttp::Response] response
|
65
|
+
def put_timing(response)
|
66
|
+
response.time_started = @time_started
|
67
|
+
response.time_ended = @time_ended
|
68
|
+
response.time_connected = @time_connected
|
69
|
+
response.time_total = @time_total
|
70
|
+
response.time_connecting = @time_connecting
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_log
|
74
|
+
path = build_path(@request.request_url)
|
75
|
+
version = @response&.version || '1.1'
|
76
|
+
|
77
|
+
tls = 'None'
|
78
|
+
if @tls_config
|
79
|
+
tls = "#{@tls_config[:version]} Cipher: #{@tls_config[:cipher]}"
|
80
|
+
end
|
81
|
+
|
82
|
+
buffer = String.new
|
83
|
+
buffer << <<~TXT
|
84
|
+
MintHttp Log (#{@request.request_url})
|
85
|
+
@@ Timeouts: #{@request.open_timeout}, #{@request.write_timeout}, #{@request.read_timeout}
|
86
|
+
@@ Time: #{@time_started.round(3)} -> #{@time_connected.round(3)} connecting: #{time_connecting.round(3)} total: #{@time_total.round(3)} seconds
|
87
|
+
@@ TLS: #{tls}
|
88
|
+
-> #{@request.method.upcase} #{path} HTTP/#{version}
|
89
|
+
#{masked_headers(@net_request.each_header.to_h, '-> ')}
|
90
|
+
-> #{masked_body(@request.body, @request.headers['content-type'])}
|
91
|
+
=======
|
92
|
+
TXT
|
93
|
+
|
94
|
+
if @response
|
95
|
+
buffer << <<~TXT
|
96
|
+
<- Response: HTTP/#{@response.version} #{@response.status_code} #{@response.status_text}
|
97
|
+
#{masked_headers(@response.headers, '<- ')}
|
98
|
+
<- Length: #{@response.body.bytesize} Body: #{masked_body(@response.body, @response.headers['content-type'])}
|
99
|
+
TXT
|
100
|
+
end
|
101
|
+
|
102
|
+
if @error
|
103
|
+
buffer << "!! Error: #{@error.class}, message: #{@error.message}"
|
104
|
+
end
|
105
|
+
|
106
|
+
unless buffer.valid_encoding?
|
107
|
+
raise 'Buffer has not valid encoding'
|
108
|
+
end
|
109
|
+
|
110
|
+
@logger.info(buffer.strip)
|
111
|
+
end
|
112
|
+
|
113
|
+
def log_connection_info(http)
|
114
|
+
@tls_config = http.instance_eval do
|
115
|
+
if use_ssl?
|
116
|
+
{ version: @socket.io.ssl_version, cipher: @socket.io.cipher[0] }
|
117
|
+
else
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def clock_time
|
126
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
127
|
+
end
|
128
|
+
|
129
|
+
def build_path(url)
|
130
|
+
full_path = url.path.dup
|
131
|
+
|
132
|
+
if url.query
|
133
|
+
full_path << "?#{url.query}"
|
134
|
+
end
|
135
|
+
|
136
|
+
if url.fragment
|
137
|
+
full_path << "##{url.fragment}"
|
138
|
+
end
|
139
|
+
|
140
|
+
full_path
|
141
|
+
end
|
142
|
+
|
143
|
+
def lower_case_filter_list
|
144
|
+
@_lower_case_filter_list ||= @filter_list.map(&:downcase)
|
145
|
+
end
|
146
|
+
|
147
|
+
def filter_query(query)
|
148
|
+
unless @filter
|
149
|
+
return query
|
150
|
+
end
|
151
|
+
|
152
|
+
filtered = CGI::parse(query || '')
|
153
|
+
.to_h { |k, v| [k, lower_case_filter_list.include?(v) ? '[FILTERED]' : v] }
|
154
|
+
|
155
|
+
URI.encode_www_form(filtered)
|
156
|
+
end
|
157
|
+
|
158
|
+
def masked_headers(headers, prefix = '')
|
159
|
+
headers
|
160
|
+
.map { |k, v| [k.split('-').map(&:capitalize).join('-'), v] }
|
161
|
+
.map { |k, v| "#{prefix}#{k}: #{@filter && lower_case_filter_list.include?(k.downcase) ? '[FILTERED]' : v}" }
|
162
|
+
.join("\n")
|
163
|
+
end
|
164
|
+
|
165
|
+
def masked_body(body, type)
|
166
|
+
size = body&.bytesize || 0
|
167
|
+
if size == 0
|
168
|
+
return '[EMPTY]'
|
169
|
+
end
|
170
|
+
|
171
|
+
if size > MAX_BODY_SIZE
|
172
|
+
return '[LARGE]'
|
173
|
+
end
|
174
|
+
|
175
|
+
unless body_allowed?(type)
|
176
|
+
return '[COMPLEX]'
|
177
|
+
end
|
178
|
+
|
179
|
+
unless @filter
|
180
|
+
return body
|
181
|
+
end
|
182
|
+
|
183
|
+
if type.match?(/json/)
|
184
|
+
redact_json(body)
|
185
|
+
elsif type.match?(/xml/)
|
186
|
+
redact_xml(body)
|
187
|
+
else
|
188
|
+
body
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def body_allowed?(content_type)
|
193
|
+
BODY_ALLOWED_TYPES.any? { |pattern| pattern.match?(content_type) }
|
194
|
+
end
|
195
|
+
|
196
|
+
def redact_json(json)
|
197
|
+
@json_patterns ||= @filter_list.map do |keyword|
|
198
|
+
keyword = Regexp.escape(keyword)
|
199
|
+
Regexp.compile("\"(#{keyword})\"(\\s*):(\\s*)(?>\".+?(?<!\\\\)\"|\\d+(?>\\.\\d+)?)", Regexp::IGNORECASE | Regexp::EXTENDED)
|
200
|
+
end
|
201
|
+
|
202
|
+
@json_patterns.inject(json) do |carry, pattern|
|
203
|
+
carry.gsub(pattern, '"\1"\2:\3"[FILTERED]"')
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def redact_xml(raw)
|
208
|
+
@xml_patterns ||= @filter_list.map do |keyword|
|
209
|
+
keyword = Regexp.escape(keyword)
|
210
|
+
Regexp.compile("<#{keyword}(?>.|\\n)+?</(#{keyword})>", Regexp::IGNORECASE | Regexp::EXTENDED)
|
211
|
+
end
|
212
|
+
|
213
|
+
@xml_patterns.inject(raw) do |carry, pattern|
|
214
|
+
carry.gsub(pattern, '<\1>[FILTERED]</\1>')
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
data/lib/mint_http/response.rb
CHANGED
@@ -2,14 +2,47 @@
|
|
2
2
|
|
3
3
|
module MintHttp
|
4
4
|
class Response
|
5
|
+
# @!attribute [r] net_request
|
6
|
+
# @return [Net::HTTPRequest]
|
7
|
+
attr_reader :net_request
|
8
|
+
|
9
|
+
# @!attribute [r] net_response
|
10
|
+
# @return [Net::HTTPResponse]
|
5
11
|
attr_reader :net_response
|
12
|
+
|
13
|
+
# @!attribute [r] mint_request
|
14
|
+
# @return [MintHttp::Request]
|
15
|
+
attr_reader :mint_request
|
16
|
+
|
17
|
+
# @!attribute [r] version
|
18
|
+
# @return [String]
|
6
19
|
attr_reader :version
|
20
|
+
|
21
|
+
# @!attribute [r] status_code
|
22
|
+
# @return [Integer]
|
7
23
|
attr_reader :status_code
|
24
|
+
|
25
|
+
# @!attribute [r] status_text
|
26
|
+
# @return [String]
|
8
27
|
attr_reader :status_text
|
28
|
+
|
29
|
+
# @!attribute [r] headers
|
30
|
+
# @return [Hash<String,Array[String]>]
|
9
31
|
attr_reader :headers
|
10
32
|
|
11
|
-
|
33
|
+
attr_accessor :time_started
|
34
|
+
attr_accessor :time_ended
|
35
|
+
attr_accessor :time_connected
|
36
|
+
attr_accessor :time_total
|
37
|
+
attr_accessor :time_connecting
|
38
|
+
|
39
|
+
# @param [Net::HTTPResponse] net_response
|
40
|
+
# @param [Net::HTTPRequest] net_request
|
41
|
+
# @param [MintHttp::Request] mint_request
|
42
|
+
def initialize(net_response, net_request, mint_request)
|
12
43
|
@net_response = net_response
|
44
|
+
@net_request = net_request
|
45
|
+
@mint_request = mint_request
|
13
46
|
@version = net_response.http_version
|
14
47
|
@status_code = net_response.code.to_i
|
15
48
|
@status_text = net_response.message
|
@@ -69,6 +102,18 @@ module MintHttp
|
|
69
102
|
@json ||= JSON.parse(body)
|
70
103
|
end
|
71
104
|
|
105
|
+
def json?
|
106
|
+
headers['content-type']&.include?('application/json')
|
107
|
+
end
|
108
|
+
|
109
|
+
def xml?
|
110
|
+
headers['content-type']&.match?(/\/xml/)
|
111
|
+
end
|
112
|
+
|
113
|
+
def https?
|
114
|
+
@net_request
|
115
|
+
end
|
116
|
+
|
72
117
|
def inspect
|
73
118
|
"#<#{self.class}/#{@version} #{@status_code} #{@status_text} #{@headers.inspect}>"
|
74
119
|
end
|
data/lib/mint_http/version.rb
CHANGED
data/lib/mint_http.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'logger'
|
4
|
+
|
3
5
|
require_relative 'mint_http/version'
|
6
|
+
require_relative 'mint_http/config'
|
4
7
|
require_relative 'mint_http/pool_entry'
|
5
8
|
require_relative 'mint_http/pool'
|
6
9
|
require_relative 'mint_http/headers'
|
@@ -11,11 +14,24 @@ require_relative 'mint_http/errors/authorization_error'
|
|
11
14
|
require_relative 'mint_http/errors/authentication_error'
|
12
15
|
require_relative 'mint_http/errors/not_found_error'
|
13
16
|
require_relative 'mint_http/net_http_factory'
|
17
|
+
require_relative 'mint_http/request_logger'
|
14
18
|
require_relative 'mint_http/response'
|
15
19
|
require_relative 'mint_http/request'
|
16
20
|
|
17
21
|
module MintHttp
|
18
22
|
class << self
|
23
|
+
def init_mint
|
24
|
+
config.logger = Logger.new('/dev/null')
|
25
|
+
config.filter_params_list = []
|
26
|
+
config.filter_params = false
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [MintHttp::Config]
|
30
|
+
# noinspection RbsMissingTypeSignature,RubyClassVariableUsageInspection
|
31
|
+
def config
|
32
|
+
@@config ||= MintHttp::Config.new
|
33
|
+
end
|
34
|
+
|
19
35
|
# @return [::MintHttp::Request]
|
20
36
|
def method_missing(method, *args)
|
21
37
|
request = Request.new
|
@@ -23,3 +39,5 @@ module MintHttp
|
|
23
39
|
end
|
24
40
|
end
|
25
41
|
end
|
42
|
+
|
43
|
+
MintHttp.init_mint
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mint_http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ali Alhoshaiyan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-http
|
@@ -95,6 +95,7 @@ files:
|
|
95
95
|
- README.md
|
96
96
|
- Rakefile
|
97
97
|
- lib/mint_http.rb
|
98
|
+
- lib/mint_http/config.rb
|
98
99
|
- lib/mint_http/errors/authentication_error.rb
|
99
100
|
- lib/mint_http/errors/authorization_error.rb
|
100
101
|
- lib/mint_http/errors/client_error.rb
|
@@ -106,6 +107,7 @@ files:
|
|
106
107
|
- lib/mint_http/pool.rb
|
107
108
|
- lib/mint_http/pool_entry.rb
|
108
109
|
- lib/mint_http/request.rb
|
110
|
+
- lib/mint_http/request_logger.rb
|
109
111
|
- lib/mint_http/response.rb
|
110
112
|
- lib/mint_http/version.rb
|
111
113
|
- sig/mint_http.rbs
|
@@ -130,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
132
|
- !ruby/object:Gem::Version
|
131
133
|
version: '0'
|
132
134
|
requirements: []
|
133
|
-
rubygems_version: 3.
|
135
|
+
rubygems_version: 3.4.10
|
134
136
|
signing_key:
|
135
137
|
specification_version: 4
|
136
138
|
summary: A small fluent HTTP client.
|