net-hippie 1.4.0 → 1.5.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -1
- data/CHANGELOG.md +61 -30
- data/README.md +1 -5
- data/lib/net/hippie/client.rb +75 -67
- data/lib/net/hippie/connection.rb +62 -20
- data/lib/net/hippie/connection_pool.rb +59 -0
- data/lib/net/hippie/dns_cache.rb +45 -0
- data/lib/net/hippie/tls_parser.rb +20 -0
- data/lib/net/hippie/version.rb +1 -1
- data/lib/net/hippie.rb +44 -30
- data/net-hippie.gemspec +16 -10
- data/sig/net/hippie/client.rbs +25 -0
- data/sig/net/hippie/content_type_mapper.rbs +7 -0
- data/sig/net/hippie.rbs +29 -0
- metadata +91 -14
- data/.github/dependabot.yml +0 -9
- data/.github/workflows/ci.yml +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f35e1b41d142150ed382fbe5c94a5d9955771831a10e55115b8ae3031e9272ca
|
|
4
|
+
data.tar.gz: 78f0d65b5efe836b3a87e24c90e0eb699d7aebfb9433af04938be044f0f004d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f318a8981fd62ca7c99ab335fc6bbe0075af82f7740a46476eb8cadc5b2fb9eb2d18f1239461af8e59925e52b95fde12539b7434a0b987ca1505e1b8faf0334
|
|
7
|
+
data.tar.gz: e8e02a3da9d16aab10fc2c12d568734a8377e967cb294aa40261f2f64472603630ab3ca187e28569e72bc9595c75c11d38b15f675494b9f27f6a0a06293d9718
|
data/.rubocop.yml
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
plugins:
|
|
2
|
+
- rubocop-minitest
|
|
3
|
+
- rubocop-rake
|
|
4
|
+
|
|
1
5
|
AllCops:
|
|
2
6
|
Exclude:
|
|
3
7
|
- 'coverage/**/*'
|
|
@@ -6,7 +10,7 @@ AllCops:
|
|
|
6
10
|
- 'tmp/**/*'
|
|
7
11
|
- 'vendor/**/*'
|
|
8
12
|
NewCops: enable
|
|
9
|
-
TargetRubyVersion:
|
|
13
|
+
TargetRubyVersion: 4.0
|
|
10
14
|
|
|
11
15
|
Gemspec/DevelopmentDependencies:
|
|
12
16
|
EnforcedStyle: gemspec
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
Version 1.3.0
|
|
2
|
-
|
|
3
1
|
# Changelog
|
|
4
2
|
All notable changes to this project will be documented in this file.
|
|
5
3
|
|
|
@@ -8,6 +6,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
6
|
|
|
9
7
|
## [Unreleased]
|
|
10
8
|
|
|
9
|
+
## [1.5.1] - 2026-02-01
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fix load order to require version before client
|
|
14
|
+
|
|
15
|
+
## [1.5.0] - 2026-02-01
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Prefer IPv4 addresses in DNS resolution to avoid connectivity issues
|
|
20
|
+
- Increase default `keep_alive_timeout` to 30 seconds for better connection reuse
|
|
21
|
+
- Set TLS `min_version` to TLS 1.2 by default for improved security
|
|
22
|
+
- Improve retry logging format with structured error details
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Add thread-safe connection pooling with LRU eviction
|
|
27
|
+
- Add DNS pre-resolution with configurable timeout to prevent indefinite hangs
|
|
28
|
+
- Add DNS caching with TTL support
|
|
29
|
+
- Add persistent HTTP sessions to avoid `Connection: close` overhead
|
|
30
|
+
- Add `head` and `options` HTTP methods
|
|
31
|
+
- Add `close_all` method to release all pooled connections
|
|
32
|
+
- Add `reset_default_client!` method to reset the default client
|
|
33
|
+
- Add configuration options: `keep_alive_timeout`, `max_retries`, `min_version`, `continue_timeout`, `ignore_eof`, `max_connections`, `dns_timeout`, `dns_ttl`
|
|
34
|
+
- Parse TLS certificates once at initialization for performance
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Fix connection leak when racing threads create duplicate connections
|
|
39
|
+
|
|
11
40
|
## [1.4.0] - 2025-10-08
|
|
12
41
|
### Added
|
|
13
42
|
- Streaming response support via block parameter
|
|
@@ -100,31 +129,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
100
129
|
- with\_retry.
|
|
101
130
|
- authorization header helpers
|
|
102
131
|
|
|
103
|
-
[Unreleased]: https://
|
|
104
|
-
[1.
|
|
105
|
-
[1.
|
|
106
|
-
[1.
|
|
107
|
-
[1.
|
|
108
|
-
[1.
|
|
109
|
-
[1.
|
|
110
|
-
[1.
|
|
111
|
-
[0.
|
|
112
|
-
[0.
|
|
113
|
-
[0.3.
|
|
114
|
-
[0.
|
|
115
|
-
[0.
|
|
116
|
-
[0.2.
|
|
117
|
-
[0.2.
|
|
118
|
-
[0.2.
|
|
119
|
-
[0.2.
|
|
120
|
-
[0.2.
|
|
121
|
-
[0.2.
|
|
122
|
-
[0.1
|
|
123
|
-
[0.
|
|
124
|
-
[0.1.
|
|
125
|
-
[0.1.
|
|
126
|
-
[0.1.
|
|
127
|
-
[0.1.
|
|
128
|
-
[0.1.
|
|
129
|
-
[0.1.
|
|
130
|
-
[0.1.
|
|
132
|
+
[Unreleased]: https://src.mokhan.ca/xlgmokha/net-hippie/
|
|
133
|
+
[1.5.1]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v1.5.0...v1.5.1
|
|
134
|
+
[1.5.0]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v1.4.0...v1.5.0
|
|
135
|
+
[1.4.0]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v1.3.0...v1.4.0
|
|
136
|
+
[1.3.0]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v1.2.0...v1.3.0
|
|
137
|
+
[1.2.0]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v1.1.1...v1.2.0
|
|
138
|
+
[1.1.1]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v1.1.0...v1.1.1
|
|
139
|
+
[1.1.0]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v1.0.1...v1.1.0
|
|
140
|
+
[1.0.1]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v1.0.0...v1.0.1
|
|
141
|
+
[1.0.0]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.3.2...v1.0.0
|
|
142
|
+
[0.3.2]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.3.1...v0.3.2
|
|
143
|
+
[0.3.1]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.3.0...v0.3.1
|
|
144
|
+
[0.3.0]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.2.7...v0.3.0
|
|
145
|
+
[0.2.7]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.2.6...v0.2.7
|
|
146
|
+
[0.2.6]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.2.5...v0.2.6
|
|
147
|
+
[0.2.5]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.2.4...v0.2.5
|
|
148
|
+
[0.2.4]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.2.3...v0.2.4
|
|
149
|
+
[0.2.3]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.2.2...v0.2.3
|
|
150
|
+
[0.2.2]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.2.1...v0.2.2
|
|
151
|
+
[0.2.1]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.2.0...v0.2.1
|
|
152
|
+
[0.2.0]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.1.9...v0.2.0
|
|
153
|
+
[0.1.9]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.1.8...v0.1.9
|
|
154
|
+
[0.1.8]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.1.7...v0.1.8
|
|
155
|
+
[0.1.7]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.1.6...v0.1.7
|
|
156
|
+
[0.1.6]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.1.5...v0.1.6
|
|
157
|
+
[0.1.5]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.1.4...v0.1.5
|
|
158
|
+
[0.1.4]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.1.3...v0.1.4
|
|
159
|
+
[0.1.3]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.1.2...v0.1.3
|
|
160
|
+
[0.1.2]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.1.1...v0.1.2
|
|
161
|
+
[0.1.1]: https://src.mokhan.ca/xlgmokha/net-hippie/compare/v0.1.0...v0.1.1
|
data/README.md
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# Net::Hippie
|
|
2
2
|
|
|
3
|
-
[](https://rubygems.org/gems/net-hippie)
|
|
4
|
-
[)](https://github.com/xlgmokha/net-hippie/actions)
|
|
5
|
-
|
|
6
|
-
|
|
7
3
|
`Net::Hippie` is a light weight wrapper around `net/http` that defaults to
|
|
8
4
|
sending `JSON` messages.
|
|
9
5
|
|
|
@@ -136,7 +132,7 @@ push git commits and tags, and push the `.gem` file to [rubygems.org](https://ru
|
|
|
136
132
|
|
|
137
133
|
## Contributing
|
|
138
134
|
|
|
139
|
-
|
|
135
|
+
Patches are welcome at https://src.mokhan.ca/xlgmokha/net-hippie.
|
|
140
136
|
|
|
141
137
|
## License
|
|
142
138
|
|
data/lib/net/hippie/client.rb
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'net/hippie/dns_cache'
|
|
4
|
+
require 'net/hippie/tls_parser'
|
|
5
|
+
|
|
3
6
|
module Net
|
|
4
7
|
module Hippie
|
|
5
|
-
#
|
|
8
|
+
# HTTP client with connection pooling, DNS caching, and retry logic.
|
|
6
9
|
class Client
|
|
10
|
+
include TlsParser
|
|
11
|
+
|
|
7
12
|
DEFAULT_HEADERS = {
|
|
8
13
|
'Accept' => 'application/json',
|
|
9
14
|
'Content-Type' => 'application/json',
|
|
10
|
-
'User-Agent' => "net/hippie #{
|
|
15
|
+
'User-Agent' => "net/hippie #{VERSION}"
|
|
11
16
|
}.freeze
|
|
12
17
|
|
|
18
|
+
JITTER = Random.new.freeze
|
|
19
|
+
|
|
13
20
|
attr_reader :mapper, :logger, :follow_redirects
|
|
14
21
|
|
|
15
22
|
def initialize(options = {})
|
|
@@ -17,102 +24,103 @@ module Net
|
|
|
17
24
|
@mapper = options.fetch(:mapper, ContentTypeMapper.new)
|
|
18
25
|
@logger = options.fetch(:logger, Net::Hippie.logger)
|
|
19
26
|
@follow_redirects = options.fetch(:follow_redirects, 0)
|
|
20
|
-
@default_headers = options.fetch(:headers, DEFAULT_HEADERS)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
hash[key] = Connection.new(scheme, host, port, options)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def execute(uri, request, limit: follow_redirects, &block)
|
|
28
|
-
connection = connection_for(uri)
|
|
29
|
-
return execute_with_block(connection, request, &block) if block_given?
|
|
30
|
-
|
|
31
|
-
response = connection.run(request)
|
|
32
|
-
follow_redirect?(response, limit) ? follow_redirect(connection, response, limit) : response
|
|
27
|
+
@default_headers = options.fetch(:headers, DEFAULT_HEADERS).freeze
|
|
28
|
+
configure_pool(options)
|
|
29
|
+
configure_tls(options)
|
|
33
30
|
end
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
%i[get post put patch delete].each do |method|
|
|
33
|
+
define_method(method) do |uri, headers: {}, body: {}, stream: false, &block|
|
|
34
|
+
run(uri, Net::HTTP.const_get(method.capitalize), headers, body, stream, &block)
|
|
35
|
+
end
|
|
37
36
|
end
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
%i[head options].each do |method|
|
|
39
|
+
define_method(method) do |uri, headers: {}, &block|
|
|
40
|
+
run(uri, Net::HTTP.const_get(method.capitalize), headers, {}, false, &block)
|
|
41
|
+
end
|
|
41
42
|
end
|
|
42
43
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
def execute(uri, request, limit: follow_redirects, stream: false, &block)
|
|
45
|
+
conn = connection_for(uri)
|
|
46
|
+
return conn.run(request) { |res| res.read_body(&block) } if stream && block
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
run(uri, Net::HTTP::Put, headers, body, &block)
|
|
49
|
-
end
|
|
48
|
+
return execute_with_block(conn, request, &block) if block
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
response = conn.run(request)
|
|
51
|
+
limit.positive? && response.is_a?(Net::HTTPRedirection) ? follow_redirect(uri, response, limit) : response
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
# attempt 1 -> delay 0.1 second
|
|
56
|
-
# attempt 2 -> delay 0.2 second
|
|
57
|
-
# attempt 3 -> delay 0.4 second
|
|
58
|
-
# attempt 4 -> delay 0.8 second
|
|
59
|
-
# attempt 5 -> delay 1.6 second
|
|
60
|
-
# attempt 6 -> delay 3.2 second
|
|
61
|
-
# attempt 7 -> delay 6.4 second
|
|
62
|
-
# attempt 8 -> delay 12.8 second
|
|
63
54
|
def with_retry(retries: 3)
|
|
64
|
-
retries =
|
|
55
|
+
retries = [retries.to_i, 0].max
|
|
56
|
+
0.upto(retries) do |attempt|
|
|
57
|
+
return yield self
|
|
58
|
+
rescue *CONNECTION_ERRORS => error
|
|
59
|
+
raise if attempt == retries
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
attempt(n, retries) do
|
|
68
|
-
return yield self
|
|
69
|
-
end
|
|
61
|
+
sleep_with_backoff(attempt, retries, error)
|
|
70
62
|
end
|
|
71
63
|
end
|
|
72
64
|
|
|
65
|
+
def close_all
|
|
66
|
+
@pool.close_all
|
|
67
|
+
@dns_cache.clear
|
|
68
|
+
end
|
|
69
|
+
|
|
73
70
|
private
|
|
74
71
|
|
|
75
|
-
|
|
72
|
+
def configure_pool(options)
|
|
73
|
+
@dns_ttl = options.fetch(:dns_ttl, 300)
|
|
74
|
+
@dns_cache = DnsCache.new(
|
|
75
|
+
timeout: options.fetch(:dns_timeout, 5), ttl: @dns_ttl, logger: @logger
|
|
76
|
+
)
|
|
77
|
+
@pool = ConnectionPool.new(max_size: options.fetch(:max_connections, 100), dns_ttl: @dns_ttl)
|
|
78
|
+
end
|
|
76
79
|
|
|
77
|
-
def
|
|
78
|
-
|
|
80
|
+
def configure_tls(options)
|
|
81
|
+
@tls_cert = parse_cert(options[:certificate])
|
|
82
|
+
@tls_key = parse_key(options[:key], options[:passphrase])
|
|
83
|
+
@continue_timeout = options[:continue_timeout]
|
|
84
|
+
@ignore_eof = options.fetch(:ignore_eof, true)
|
|
79
85
|
end
|
|
80
86
|
|
|
81
|
-
def
|
|
82
|
-
|
|
87
|
+
def run(uri, method, headers, body, stream, &block)
|
|
88
|
+
uri = URI.parse(uri.to_s) unless uri.is_a?(URI::Generic)
|
|
89
|
+
execute(uri, build_request(method, uri, headers, body), stream: stream, &block)
|
|
83
90
|
end
|
|
84
91
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
def build_request(type, uri, headers, body)
|
|
93
|
+
merged = headers.empty? ? @default_headers : @default_headers.merge(headers)
|
|
94
|
+
path = uri.respond_to?(:request_uri) ? uri.request_uri : uri.path
|
|
95
|
+
type.new(path, merged).tap { |req| req.body = @mapper.map_from(merged, body) unless body.empty? }
|
|
89
96
|
end
|
|
90
97
|
|
|
91
|
-
def
|
|
92
|
-
yield
|
|
93
|
-
|
|
94
|
-
raise error if attempt == max
|
|
98
|
+
def execute_with_block(conn, request, &block)
|
|
99
|
+
block.arity == 2 ? yield(request, conn.run(request)) : conn.run(request, &block)
|
|
100
|
+
end
|
|
95
101
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
def connection_for(uri)
|
|
103
|
+
uri = URI.parse(uri.to_s) unless uri.is_a?(URI::Generic)
|
|
104
|
+
key = [uri.scheme, uri.host, uri.port]
|
|
105
|
+
@pool.checkout(key) { Connection.new(uri.scheme, uri.host, uri.port, @dns_cache.resolve(uri.host), conn_opts) }
|
|
99
106
|
end
|
|
100
107
|
|
|
101
|
-
def
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
end
|
|
108
|
+
def sleep_with_backoff(attempt, max_retries, error)
|
|
109
|
+
delay = ((2**attempt) * 0.1) + JITTER.rand(0.05)
|
|
110
|
+
logger&.warn("[Hippie] #{error.class}: #{error.message} | Retry #{attempt + 1}/#{max_retries}")
|
|
111
|
+
sleep delay
|
|
106
112
|
end
|
|
107
113
|
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
def conn_opts
|
|
115
|
+
@options.merge(
|
|
116
|
+
tls_cert: @tls_cert, tls_key: @tls_key,
|
|
117
|
+
continue_timeout: @continue_timeout, ignore_eof: @ignore_eof
|
|
118
|
+
)
|
|
111
119
|
end
|
|
112
120
|
|
|
113
|
-
def
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
def follow_redirect(original_uri, response, limit)
|
|
122
|
+
redirect_uri = original_uri.merge(response['location'])
|
|
123
|
+
execute(redirect_uri, build_request(Net::HTTP::Get, redirect_uri, {}, {}), limit: limit - 1)
|
|
116
124
|
end
|
|
117
125
|
end
|
|
118
126
|
end
|
|
@@ -2,27 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
module Net
|
|
4
4
|
module Hippie
|
|
5
|
-
#
|
|
5
|
+
# Persistent HTTP connection with automatic reconnection.
|
|
6
6
|
class Connection
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
http
|
|
13
|
-
http.set_debug_output(options[:logger]) if options[:logger]
|
|
14
|
-
apply_client_tls_to(http, options)
|
|
15
|
-
@http = http
|
|
7
|
+
RETRYABLE_ERRORS = [EOFError, Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError].freeze
|
|
8
|
+
|
|
9
|
+
def initialize(scheme, host, port, ipaddr, options = {})
|
|
10
|
+
@mutex = Mutex.new
|
|
11
|
+
@created_at = Time.now
|
|
12
|
+
@http = build_http(scheme, host, port, ipaddr, options)
|
|
16
13
|
end
|
|
17
14
|
|
|
18
15
|
def run(request, &block)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@http.request(request)
|
|
16
|
+
@mutex.synchronize do
|
|
17
|
+
ensure_started
|
|
18
|
+
execute(request, &block)
|
|
23
19
|
end
|
|
24
20
|
end
|
|
25
21
|
|
|
22
|
+
def stale?(ttl)
|
|
23
|
+
(Time.now - @created_at) > ttl
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def close
|
|
27
|
+
@mutex.synchronize { @http.finish if @http.started? }
|
|
28
|
+
rescue IOError
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
26
32
|
def build_url_for(path)
|
|
27
33
|
return path if path.start_with?('http')
|
|
28
34
|
|
|
@@ -31,15 +37,51 @@ module Net
|
|
|
31
37
|
|
|
32
38
|
private
|
|
33
39
|
|
|
34
|
-
def
|
|
35
|
-
|
|
40
|
+
def build_http(scheme, host, port, ipaddr, options)
|
|
41
|
+
Net::HTTP.new(host, port).tap do |http|
|
|
42
|
+
configure_timeouts(http, options)
|
|
43
|
+
configure_ssl(http, scheme, options)
|
|
44
|
+
configure_tls_client(http, options)
|
|
45
|
+
http.ipaddr = ipaddr if ipaddr
|
|
46
|
+
end
|
|
47
|
+
end
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
http.
|
|
49
|
+
def configure_timeouts(http, options)
|
|
50
|
+
http.open_timeout = options.fetch(:open_timeout, 10)
|
|
51
|
+
http.read_timeout = options.fetch(:read_timeout, 10)
|
|
52
|
+
http.write_timeout = options.fetch(:write_timeout, 10)
|
|
53
|
+
http.keep_alive_timeout = options.fetch(:keep_alive_timeout, 30)
|
|
54
|
+
http.max_retries = options.fetch(:max_retries, 1)
|
|
55
|
+
http.continue_timeout = options[:continue_timeout] if options[:continue_timeout]
|
|
56
|
+
http.ignore_eof = options.fetch(:ignore_eof, true)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def configure_ssl(http, scheme, options)
|
|
60
|
+
http.use_ssl = scheme == 'https'
|
|
61
|
+
return unless http.use_ssl?
|
|
62
|
+
|
|
63
|
+
http.min_version = options.fetch(:min_version, :TLS1_2)
|
|
64
|
+
http.verify_mode = options.fetch(:verify_mode, Net::Hippie.verify_mode)
|
|
65
|
+
http.set_debug_output(options[:logger]) if options[:logger]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def configure_tls_client(http, options)
|
|
69
|
+
http.cert = options[:tls_cert] if options[:tls_cert]
|
|
70
|
+
http.key = options[:tls_key] if options[:tls_key]
|
|
39
71
|
end
|
|
40
72
|
|
|
41
|
-
def
|
|
42
|
-
|
|
73
|
+
def ensure_started
|
|
74
|
+
@http.start unless @http.started?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def execute(request, retried: false, &block)
|
|
78
|
+
block ? @http.request(request, &block) : @http.request(request)
|
|
79
|
+
rescue *RETRYABLE_ERRORS
|
|
80
|
+
raise if retried
|
|
81
|
+
|
|
82
|
+
@http.finish if @http.started?
|
|
83
|
+
@http.start
|
|
84
|
+
execute(request, retried: true, &block)
|
|
43
85
|
end
|
|
44
86
|
end
|
|
45
87
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
module Hippie
|
|
5
|
+
# Thread-safe connection pool with LRU eviction.
|
|
6
|
+
class ConnectionPool
|
|
7
|
+
def initialize(max_size: 100, dns_ttl: 300)
|
|
8
|
+
@max_size = max_size
|
|
9
|
+
@dns_ttl = dns_ttl
|
|
10
|
+
@connections = {}
|
|
11
|
+
@monitor = Monitor.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def checkout(key, &block)
|
|
15
|
+
reuse(key) || create(key, &block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def close_all
|
|
19
|
+
@monitor.synchronize do
|
|
20
|
+
@connections.each_value(&:close)
|
|
21
|
+
@connections.clear
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def reuse(key)
|
|
28
|
+
@monitor.synchronize do
|
|
29
|
+
return nil unless @connections.key?(key)
|
|
30
|
+
|
|
31
|
+
conn = @connections.delete(key)
|
|
32
|
+
return @connections[key] = conn unless conn.stale?(@dns_ttl)
|
|
33
|
+
|
|
34
|
+
conn.close
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def create(key)
|
|
40
|
+
conn = yield
|
|
41
|
+
@monitor.synchronize do
|
|
42
|
+
existing = reuse(key)
|
|
43
|
+
if existing
|
|
44
|
+
conn.close
|
|
45
|
+
return existing
|
|
46
|
+
end
|
|
47
|
+
evict_lru if @connections.size >= @max_size
|
|
48
|
+
@connections[key] = conn
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def evict_lru
|
|
53
|
+
key, conn = @connections.first
|
|
54
|
+
conn.close
|
|
55
|
+
@connections.delete(key)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
module Hippie
|
|
5
|
+
# Thread-safe DNS resolution cache with TTL.
|
|
6
|
+
class DnsCache
|
|
7
|
+
def initialize(timeout: 5, ttl: 300, logger: nil)
|
|
8
|
+
@timeout = timeout
|
|
9
|
+
@ttl = ttl
|
|
10
|
+
@logger = logger
|
|
11
|
+
@cache = {}
|
|
12
|
+
@monitor = Monitor.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def resolve(hostname)
|
|
16
|
+
cached = get(hostname)
|
|
17
|
+
return cached if cached
|
|
18
|
+
|
|
19
|
+
ip = Net::Hippie.resolve(hostname, timeout: @timeout)
|
|
20
|
+
set(hostname, ip)
|
|
21
|
+
ip
|
|
22
|
+
rescue Timeout::Error, Resolv::ResolvError => error
|
|
23
|
+
@logger&.warn("[Hippie] DNS resolution failed for #{hostname}: #{error.message}")
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def clear
|
|
28
|
+
@monitor.synchronize { @cache.clear }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def get(hostname)
|
|
34
|
+
@monitor.synchronize do
|
|
35
|
+
entry = @cache[hostname]
|
|
36
|
+
entry[:ip] if entry && (Time.now - entry[:time]) < @ttl
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set(hostname, ip_addr)
|
|
41
|
+
@monitor.synchronize { @cache[hostname] = { ip: ip_addr, time: Time.now } }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
module Hippie
|
|
5
|
+
# Parses TLS certificates and keys from various formats.
|
|
6
|
+
module TlsParser
|
|
7
|
+
def parse_cert(cert)
|
|
8
|
+
return cert if cert.is_a?(OpenSSL::X509::Certificate) || cert.nil?
|
|
9
|
+
|
|
10
|
+
OpenSSL::X509::Certificate.new(cert)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def parse_key(key, passphrase)
|
|
14
|
+
return key if key.is_a?(OpenSSL::PKey::PKey) || key.nil?
|
|
15
|
+
|
|
16
|
+
passphrase ? OpenSSL::PKey::RSA.new(key, passphrase) : OpenSSL::PKey::RSA.new(key)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/net/hippie/version.rb
CHANGED
data/lib/net/hippie.rb
CHANGED
|
@@ -3,69 +3,83 @@
|
|
|
3
3
|
require 'base64'
|
|
4
4
|
require 'json'
|
|
5
5
|
require 'logger'
|
|
6
|
+
require 'monitor'
|
|
6
7
|
require 'net/http'
|
|
7
8
|
require 'openssl'
|
|
9
|
+
require 'resolv'
|
|
10
|
+
require 'timeout'
|
|
8
11
|
|
|
9
12
|
require 'net/hippie/version'
|
|
10
13
|
require 'net/hippie/client'
|
|
11
14
|
require 'net/hippie/connection'
|
|
15
|
+
require 'net/hippie/connection_pool'
|
|
12
16
|
require 'net/hippie/content_type_mapper'
|
|
13
17
|
|
|
14
18
|
module Net
|
|
15
|
-
#
|
|
19
|
+
# High-performance HTTP client with connection pooling and DNS caching.
|
|
16
20
|
module Hippie
|
|
17
21
|
CONNECTION_ERRORS = [
|
|
18
22
|
EOFError,
|
|
19
23
|
Errno::ECONNREFUSED,
|
|
20
24
|
Errno::ECONNRESET,
|
|
21
|
-
Errno::ECONNRESET,
|
|
22
25
|
Errno::EHOSTUNREACH,
|
|
23
26
|
Errno::EINVAL,
|
|
27
|
+
Errno::EPIPE,
|
|
28
|
+
Errno::ECONNABORTED,
|
|
29
|
+
Errno::ETIMEDOUT,
|
|
30
|
+
IOError,
|
|
24
31
|
Net::OpenTimeout,
|
|
25
32
|
Net::ProtocolError,
|
|
26
33
|
Net::ReadTimeout,
|
|
34
|
+
Net::WriteTimeout,
|
|
27
35
|
OpenSSL::OpenSSLError,
|
|
28
36
|
OpenSSL::SSL::SSLError,
|
|
29
37
|
SocketError,
|
|
30
38
|
Timeout::Error
|
|
31
39
|
].freeze
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end
|
|
41
|
+
BASIC_PREFIX = 'Basic '
|
|
42
|
+
BEARER_PREFIX = 'Bearer '
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
end
|
|
44
|
+
class << self
|
|
45
|
+
attr_writer :logger, :verify_mode
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
def logger
|
|
48
|
+
@logger ||= Logger.new(nil)
|
|
49
|
+
end
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
def verify_mode
|
|
52
|
+
@verify_mode ||= OpenSSL::SSL::VERIFY_PEER
|
|
53
|
+
end
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
def basic_auth(username, password)
|
|
56
|
+
BASIC_PREFIX + ::Base64.strict_encode64("#{username}:#{password}")
|
|
57
|
+
end
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
def bearer_auth(token)
|
|
60
|
+
BEARER_PREFIX + token
|
|
61
|
+
end
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
end || super
|
|
61
|
-
end
|
|
63
|
+
def default_client
|
|
64
|
+
@default_client ||= Client.new(follow_redirects: 3, logger: logger)
|
|
65
|
+
end
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
def reset_default_client!
|
|
68
|
+
@default_client = nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
%i[get post put patch delete head options].each do |method|
|
|
72
|
+
define_method(method) do |*args, **kwargs, &block|
|
|
73
|
+
default_client.with_retry(retries: 3) { |c| c.public_send(method, *args, **kwargs, &block) }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
def resolve(hostname, timeout: 5)
|
|
78
|
+
Timeout.timeout(timeout) do
|
|
79
|
+
addresses = Resolv.getaddresses(hostname)
|
|
80
|
+
addresses.find { |a| a.match?(/^\d+\.\d+\.\d+\.\d+$/) } || addresses.first
|
|
81
|
+
end
|
|
82
|
+
end
|
|
69
83
|
end
|
|
70
84
|
end
|
|
71
85
|
end
|
data/net-hippie.gemspec
CHANGED
|
@@ -7,17 +7,18 @@ require 'net/hippie/version'
|
|
|
7
7
|
Gem::Specification.new do |spec|
|
|
8
8
|
spec.name = 'net-hippie'
|
|
9
9
|
spec.version = Net::Hippie::VERSION
|
|
10
|
-
spec.authors = ['mo']
|
|
10
|
+
spec.authors = ['mo khan']
|
|
11
11
|
spec.email = ['mo@mokhan.ca']
|
|
12
12
|
|
|
13
13
|
spec.summary = 'net/http for hippies. ☮️ '
|
|
14
14
|
spec.description = 'net/http for hippies. ☮️ '
|
|
15
|
-
spec.homepage = 'https://
|
|
15
|
+
spec.homepage = 'https://src.mokhan.ca/xlgmokha/net-hippie'
|
|
16
16
|
spec.license = 'MIT'
|
|
17
|
-
spec.metadata
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
19
|
+
spec.metadata['source_code_uri'] = 'https://git.mokhan.ca/xlgmokha/net-hippie.git'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://src.mokhan.ca/xlgmokha/net-hippie/blob/main/CHANGELOG.md.html'
|
|
21
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
21
22
|
|
|
22
23
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
23
24
|
f.match(%r{^(test|spec|features)/})
|
|
@@ -25,16 +26,21 @@ Gem::Specification.new do |spec|
|
|
|
25
26
|
spec.bindir = 'exe'
|
|
26
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
27
28
|
spec.require_paths = ['lib']
|
|
28
|
-
spec.required_ruby_version = Gem::Requirement.new('>=
|
|
29
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 4.0.0')
|
|
29
30
|
|
|
30
31
|
spec.add_dependency 'base64', '~> 0.1'
|
|
31
32
|
spec.add_dependency 'json', '~> 2.0'
|
|
32
33
|
spec.add_dependency 'logger', '~> 1.0'
|
|
33
|
-
spec.add_dependency '
|
|
34
|
-
spec.add_dependency '
|
|
35
|
-
spec.
|
|
34
|
+
spec.add_dependency 'monitor', '~> 0.1'
|
|
35
|
+
spec.add_dependency 'net-http', '~> 0.1'
|
|
36
|
+
spec.add_dependency 'openssl', '~> 4.0'
|
|
37
|
+
spec.add_dependency 'resolv', '~> 0.1'
|
|
38
|
+
spec.add_dependency 'timeout', '~> 0.1'
|
|
39
|
+
spec.add_development_dependency 'minitest', '~> 6.0'
|
|
36
40
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
37
41
|
spec.add_development_dependency 'rubocop', '~> 1.9'
|
|
42
|
+
spec.add_development_dependency 'rubocop-minitest', '~> 0.1'
|
|
43
|
+
spec.add_development_dependency 'rubocop-rake', '~> 0.1'
|
|
38
44
|
spec.add_development_dependency 'vcr', '~> 6.0'
|
|
39
45
|
spec.add_development_dependency 'webmock', '~> 3.4'
|
|
40
46
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Net
|
|
2
|
+
module Hippie
|
|
3
|
+
class Client
|
|
4
|
+
DEFAULT_HEADERS: Hash[String, String]
|
|
5
|
+
|
|
6
|
+
attr_reader mapper: ContentTypeMapper
|
|
7
|
+
attr_reader logger: untyped
|
|
8
|
+
attr_reader follow_redirects: Integer
|
|
9
|
+
|
|
10
|
+
def initialize: (?Hash[Symbol, untyped] options) -> void
|
|
11
|
+
|
|
12
|
+
def get: (untyped uri, ?headers: Hash[String, String], ?body: untyped, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
13
|
+
def post: (untyped uri, ?headers: Hash[String, String], ?body: untyped, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
14
|
+
def put: (untyped uri, ?headers: Hash[String, String], ?body: untyped, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
15
|
+
def patch: (untyped uri, ?headers: Hash[String, String], ?body: untyped, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
16
|
+
def delete: (untyped uri, ?headers: Hash[String, String], ?body: untyped, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
17
|
+
def head: (untyped uri, ?headers: Hash[String, String]) ?{ (untyped) -> void } -> untyped
|
|
18
|
+
def options: (untyped uri, ?headers: Hash[String, String]) ?{ (untyped) -> void } -> untyped
|
|
19
|
+
|
|
20
|
+
def execute: (untyped uri, untyped request, ?limit: Integer, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
21
|
+
def with_retry: (?retries: Integer) { (Client) -> untyped } -> untyped
|
|
22
|
+
def close_all: () -> void
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/sig/net/hippie.rbs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Net
|
|
2
|
+
module Hippie
|
|
3
|
+
VERSION: String
|
|
4
|
+
|
|
5
|
+
CONNECTION_ERRORS: Array[Class]
|
|
6
|
+
|
|
7
|
+
def self.logger: () -> untyped
|
|
8
|
+
def self.logger=: (untyped) -> untyped
|
|
9
|
+
|
|
10
|
+
def self.verify_mode: () -> Integer
|
|
11
|
+
def self.verify_mode=: (Integer) -> Integer
|
|
12
|
+
|
|
13
|
+
def self.basic_auth: (String username, String password) -> String
|
|
14
|
+
def self.bearer_auth: (String token) -> String
|
|
15
|
+
|
|
16
|
+
def self.default_client: () -> Client
|
|
17
|
+
def self.reset_default_client!: () -> nil
|
|
18
|
+
|
|
19
|
+
def self.resolve: (String hostname, ?timeout: Integer) -> String?
|
|
20
|
+
|
|
21
|
+
def self.get: (untyped uri, ?headers: Hash[String, String], ?body: untyped, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
22
|
+
def self.post: (untyped uri, ?headers: Hash[String, String], ?body: untyped, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
23
|
+
def self.put: (untyped uri, ?headers: Hash[String, String], ?body: untyped, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
24
|
+
def self.patch: (untyped uri, ?headers: Hash[String, String], ?body: untyped, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
25
|
+
def self.delete: (untyped uri, ?headers: Hash[String, String], ?body: untyped, ?stream: bool) ?{ (untyped) -> void } -> untyped
|
|
26
|
+
def self.head: (untyped uri, ?headers: Hash[String, String]) ?{ (untyped) -> void } -> untyped
|
|
27
|
+
def self.options: (untyped uri, ?headers: Hash[String, String]) ?{ (untyped) -> void } -> untyped
|
|
28
|
+
end
|
|
29
|
+
end
|
metadata
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: net-hippie
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
- mo
|
|
7
|
+
- mo khan
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
@@ -51,48 +51,90 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '1.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: monitor
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.1'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.1'
|
|
54
68
|
- !ruby/object:Gem::Dependency
|
|
55
69
|
name: net-http
|
|
56
70
|
requirement: !ruby/object:Gem::Requirement
|
|
57
71
|
requirements:
|
|
58
72
|
- - "~>"
|
|
59
73
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.
|
|
74
|
+
version: '0.1'
|
|
61
75
|
type: :runtime
|
|
62
76
|
prerelease: false
|
|
63
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
78
|
requirements:
|
|
65
79
|
- - "~>"
|
|
66
80
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0.
|
|
81
|
+
version: '0.1'
|
|
68
82
|
- !ruby/object:Gem::Dependency
|
|
69
83
|
name: openssl
|
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
|
71
85
|
requirements:
|
|
72
86
|
- - "~>"
|
|
73
87
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '
|
|
88
|
+
version: '4.0'
|
|
75
89
|
type: :runtime
|
|
76
90
|
prerelease: false
|
|
77
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
92
|
requirements:
|
|
79
93
|
- - "~>"
|
|
80
94
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: '
|
|
95
|
+
version: '4.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: resolv
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0.1'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0.1'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: timeout
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0.1'
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0.1'
|
|
82
124
|
- !ruby/object:Gem::Dependency
|
|
83
125
|
name: minitest
|
|
84
126
|
requirement: !ruby/object:Gem::Requirement
|
|
85
127
|
requirements:
|
|
86
128
|
- - "~>"
|
|
87
129
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '
|
|
130
|
+
version: '6.0'
|
|
89
131
|
type: :development
|
|
90
132
|
prerelease: false
|
|
91
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
134
|
requirements:
|
|
93
135
|
- - "~>"
|
|
94
136
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: '
|
|
137
|
+
version: '6.0'
|
|
96
138
|
- !ruby/object:Gem::Dependency
|
|
97
139
|
name: rake
|
|
98
140
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -121,6 +163,34 @@ dependencies:
|
|
|
121
163
|
- - "~>"
|
|
122
164
|
- !ruby/object:Gem::Version
|
|
123
165
|
version: '1.9'
|
|
166
|
+
- !ruby/object:Gem::Dependency
|
|
167
|
+
name: rubocop-minitest
|
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - "~>"
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '0.1'
|
|
173
|
+
type: :development
|
|
174
|
+
prerelease: false
|
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
176
|
+
requirements:
|
|
177
|
+
- - "~>"
|
|
178
|
+
- !ruby/object:Gem::Version
|
|
179
|
+
version: '0.1'
|
|
180
|
+
- !ruby/object:Gem::Dependency
|
|
181
|
+
name: rubocop-rake
|
|
182
|
+
requirement: !ruby/object:Gem::Requirement
|
|
183
|
+
requirements:
|
|
184
|
+
- - "~>"
|
|
185
|
+
- !ruby/object:Gem::Version
|
|
186
|
+
version: '0.1'
|
|
187
|
+
type: :development
|
|
188
|
+
prerelease: false
|
|
189
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
190
|
+
requirements:
|
|
191
|
+
- - "~>"
|
|
192
|
+
- !ruby/object:Gem::Version
|
|
193
|
+
version: '0.1'
|
|
124
194
|
- !ruby/object:Gem::Dependency
|
|
125
195
|
name: vcr
|
|
126
196
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -157,8 +227,6 @@ executables:
|
|
|
157
227
|
extensions: []
|
|
158
228
|
extra_rdoc_files: []
|
|
159
229
|
files:
|
|
160
|
-
- ".github/dependabot.yml"
|
|
161
|
-
- ".github/workflows/ci.yml"
|
|
162
230
|
- ".gitignore"
|
|
163
231
|
- ".rubocop.yml"
|
|
164
232
|
- CHANGELOG.md
|
|
@@ -175,14 +243,23 @@ files:
|
|
|
175
243
|
- lib/net/hippie.rb
|
|
176
244
|
- lib/net/hippie/client.rb
|
|
177
245
|
- lib/net/hippie/connection.rb
|
|
246
|
+
- lib/net/hippie/connection_pool.rb
|
|
178
247
|
- lib/net/hippie/content_type_mapper.rb
|
|
248
|
+
- lib/net/hippie/dns_cache.rb
|
|
249
|
+
- lib/net/hippie/tls_parser.rb
|
|
179
250
|
- lib/net/hippie/version.rb
|
|
180
251
|
- net-hippie.gemspec
|
|
181
|
-
|
|
252
|
+
- sig/net/hippie.rbs
|
|
253
|
+
- sig/net/hippie/client.rbs
|
|
254
|
+
- sig/net/hippie/content_type_mapper.rbs
|
|
255
|
+
homepage: https://src.mokhan.ca/xlgmokha/net-hippie
|
|
182
256
|
licenses:
|
|
183
257
|
- MIT
|
|
184
258
|
metadata:
|
|
185
|
-
|
|
259
|
+
allowed_push_host: https://rubygems.org
|
|
260
|
+
homepage_uri: https://src.mokhan.ca/xlgmokha/net-hippie
|
|
261
|
+
source_code_uri: https://git.mokhan.ca/xlgmokha/net-hippie.git
|
|
262
|
+
changelog_uri: https://src.mokhan.ca/xlgmokha/net-hippie/blob/main/CHANGELOG.md.html
|
|
186
263
|
rubygems_mfa_required: 'true'
|
|
187
264
|
rdoc_options: []
|
|
188
265
|
require_paths:
|
|
@@ -191,14 +268,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
191
268
|
requirements:
|
|
192
269
|
- - ">="
|
|
193
270
|
- !ruby/object:Gem::Version
|
|
194
|
-
version:
|
|
271
|
+
version: 4.0.0
|
|
195
272
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
196
273
|
requirements:
|
|
197
274
|
- - ">="
|
|
198
275
|
- !ruby/object:Gem::Version
|
|
199
276
|
version: '0'
|
|
200
277
|
requirements: []
|
|
201
|
-
rubygems_version:
|
|
278
|
+
rubygems_version: 4.0.5
|
|
202
279
|
specification_version: 4
|
|
203
280
|
summary: net/http for hippies. ☮️
|
|
204
281
|
test_files: []
|
data/.github/dependabot.yml
DELETED
data/.github/workflows/ci.yml
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: ci
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
branches: [main]
|
|
5
|
-
pull_request:
|
|
6
|
-
branches: [main]
|
|
7
|
-
jobs:
|
|
8
|
-
test:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
strategy:
|
|
11
|
-
matrix:
|
|
12
|
-
ruby-version: ['3.2', '3.3', '3.4']
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v2
|
|
15
|
-
- name: Set up Ruby
|
|
16
|
-
uses: ruby/setup-ruby@v1
|
|
17
|
-
with:
|
|
18
|
-
ruby-version: ${{ matrix.ruby-version }}
|
|
19
|
-
bundler-cache: true
|
|
20
|
-
- name: Running tests…
|
|
21
|
-
run: sh bin/test
|
|
22
|
-
style:
|
|
23
|
-
runs-on: ubuntu-latest
|
|
24
|
-
steps:
|
|
25
|
-
- uses: actions/checkout@v2
|
|
26
|
-
- uses: ruby/setup-ruby@v1
|
|
27
|
-
with:
|
|
28
|
-
ruby-version: '3.4'
|
|
29
|
-
bundler-cache: true
|
|
30
|
-
- name: Running style checks…
|
|
31
|
-
run: sh bin/style
|