net-http-ext 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b26da458fd1f2d62e0fc16a68223f53615f93ecdada0ef979ce8af507149c246
4
+ data.tar.gz: 1ce8f34c41d3cc5e58490915ab6118c24139e0140f5a227b8d7f86df4bc59c02
5
+ SHA512:
6
+ metadata.gz: 45dd4238afd0639c6ca112eac1623f6629662ac693c92ac76d4a107105b61159e2ed0e667c17fbfb2ccc401fe6d7b580bf4ae7c2571a594c7c21566eaca48c1e
7
+ data.tar.gz: 0b522d8f0d0b1efaa7bcd2ef2dbec07982beb40c4193a51f7256158c4c5107c30402fab90b0332a57ef2844725f458a9cc3374eb30fbc29f33fc4b9cb479cb84
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Grant Birkinbine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # net-http-ext
2
+
3
+ Safe defaults, persistent connections, thread safety, and basic logging for Ruby's Net::HTTP library
4
+
5
+ A simple wrapper around [`net/http/persistent`](https://github.com/drbrain/net-http-persistent)
@@ -0,0 +1,471 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example usage:
4
+ #
5
+ # # Basic usage
6
+ # client = Net::HTTP::Ext.new("https://api.example.com")
7
+ # response = client.get("/users")
8
+ # puts response.body
9
+ #
10
+ # # With headers and query parameters
11
+ # response = client.get("/users",
12
+ # headers: {"Authorization" => "Bearer token123"},
13
+ # params: {status: "active", limit: 10})
14
+ #
15
+ # # POST JSON data
16
+ # response = client.post("/users",
17
+ # headers: {"X-Custom-Header" => "value"},
18
+ # params: {name: "John Doe", email: "john@example.com"})
19
+ #
20
+ # # With custom timeouts
21
+ # client = Net::HTTP::Ext.new("https://api.example.com",
22
+ # open_timeout: 5, # connection establishment timeout (seconds)
23
+ # read_timeout: 10, # response read timeout (seconds)
24
+ # idle_timeout: 30, # how long to keep idle connections open (seconds)
25
+ # request_timeout: 15 # overall request timeout (seconds)
26
+ # )
27
+ #
28
+ # # With default headers (applied to all requests)
29
+ # client = Net::HTTP::Ext.new("https://api.example.com",
30
+ # default_headers: {
31
+ # "User-Agent" => "MyApp/1.0",
32
+ # "Authorization" => "Bearer default-token"
33
+ # }
34
+ # )
35
+
36
+ # Benefits:
37
+ #
38
+ # 1. Reuse connections for multiple requests
39
+ # 2. Automatically rebuild the connection if it is closed by the server
40
+ # 3. Automatically retry requests on connection failures if max_retries is set to a value >1
41
+ # 4. Easy to use and configure
42
+ # 5. Supports timeouts for the entire request (open_timeout + read_timeout) and for idle connections (idle_timeout)
43
+
44
+ require "logger"
45
+ require "net/http/persistent"
46
+ require "timeout"
47
+ require "uri"
48
+ require "json"
49
+ require_relative "version"
50
+
51
+ class Net::HTTP::Ext
52
+ include NetHTTPExt
53
+
54
+ VERB_MAP = {
55
+ head: Net::HTTP::Head,
56
+ get: Net::HTTP::Get,
57
+ post: Net::HTTP::Post,
58
+ put: Net::HTTP::Put,
59
+ delete: Net::HTTP::Delete,
60
+ patch: Net::HTTP::Patch
61
+ }.freeze
62
+
63
+ # Expose the HTTP client so that we can customize client-level settings
64
+ attr_accessor :http, :default_headers
65
+
66
+ # Create a new persistent HTTP client
67
+ #
68
+ # @param endpoint [String] Endpoint URL to send requests to
69
+ # @param name [String] Name for the client (used in logs)
70
+ # @param log [Logger] Custom logger instance (optional)
71
+ # @param default_headers [Hash] Default headers to include in all requests
72
+ # @param request_timeout [Integer, nil] Overall timeout for the entire request (nil for no timeout)
73
+ # @param max_retries [Integer] Maximum number of retries on connection failures
74
+ # @param open_timeout [Integer] Timeout in seconds for connection establishment
75
+ # @param read_timeout [Integer] Timeout in seconds for reading response
76
+ # @param idle_timeout [Integer] How long to keep idle connections open in seconds (maps to keep_alive)
77
+ # @param ssl_cert_file [String] Path to a custom CA certificate file (optional)
78
+ # @param **options Additional options passed directly to Net::HTTP::Persistent
79
+ # Example:
80
+ # client = Net::HTTP::Ext.new("https://api.example.com", proxy: URI("http://proxy.example.com:8080"))
81
+ def initialize(
82
+ endpoint,
83
+ name: nil,
84
+ log: nil,
85
+ default_headers: { "user-agent" => "Net::HTTP::Ext/#{VERSION}" },
86
+ request_timeout: 30,
87
+ max_retries: 1,
88
+ # Default timeouts
89
+ # https://github.com/ruby/net-http/blob/1df862896825af04f7bf9711b9b4613bbb77cad6/lib/net/http.rb#L1152-L1154
90
+ open_timeout: nil, # generally 60
91
+ read_timeout: nil, # generally 60
92
+ idle_timeout: 5, # set specifically for keep_alive with Net::HTTP::Persistent - if a connection is idle for this long, it will be closed and automatically reopened on the next request
93
+ # Pass through any other options to Net::HTTP::Persistent
94
+ ssl_cert_file: nil,
95
+ **options
96
+ )
97
+ @uri = URI.parse(endpoint)
98
+ @name = name || ENV.fetch("HTTP_CLIENT_NAME", "http-client")
99
+ @request_timeout = request_timeout
100
+ @max_retries = max_retries
101
+ @default_headers = normalize_headers(default_headers)
102
+ @log = log || create_logger
103
+ @ssl_cert_file = ssl_cert_file || ENV.fetch("SSL_CERT_FILE", nil)
104
+
105
+ # Create options hash for Net::HTTP::Persistent
106
+ persistent_options = {
107
+ name: @name,
108
+ open_timeout: open_timeout,
109
+ read_timeout: read_timeout,
110
+ idle_timeout: idle_timeout
111
+ }
112
+
113
+ # Merge any additional options passed through
114
+ persistent_options.merge!(options)
115
+
116
+ @http = create_http_client(persistent_options)
117
+ end
118
+
119
+ # @param path [String] The path to request
120
+ # @param headers [Hash] Additional headers for this request
121
+ # @param params [Hash] Parameters to send as query parameters
122
+ # @return [Net::HTTPResponse] The HTTP response
123
+ # @example Make a HEAD request
124
+ # client = Net::HTTP::Ext.new("https://api.example.com")
125
+ # response = client.head("/users")
126
+ def head(path, headers: {}, params: {})
127
+ request(:head, path, headers: headers, body: params)
128
+ end
129
+
130
+ # @param path [String] The path to request
131
+ # @param headers [Hash] Additional headers for this request
132
+ # @param params [Hash, String] Parameters to send with the request
133
+ # @return [Net::HTTPResponse] The HTTP response
134
+ # @example Make a simple GET request
135
+ # client = Net::HTTP::Ext.new("https://api.example.com")
136
+ # response = client.get("/users")
137
+ def get(path, headers: {}, params: {})
138
+ request(:get, path, headers: headers, body: params)
139
+ end
140
+
141
+ # @param path [String] The path to request
142
+ # @param headers [Hash] Additional headers for this request
143
+ # @param payload [Hash, String] Parameters to send as request body
144
+ # @param params [Hash] Parameters to send as query parameters (deprecated - use payload instead)
145
+ # @return [Net::HTTPResponse] The HTTP response
146
+ # @example Create a new resource
147
+ # client = Net::HTTP::Ext.new("https://api.example.com")
148
+ # response = client.post("/users", payload: {name: "John", email: "john@example.com"})
149
+ def post(path, headers: {}, params: nil, payload: nil)
150
+ request(:post, path, headers: headers, body: payload || params)
151
+ end
152
+
153
+ # @param path [String] The path to request
154
+ # @param headers [Hash] Additional headers for this request
155
+ # @param payload [Hash, String] Parameters to send as request body
156
+ # @param params [Hash] Parameters to send as query parameters (deprecated - use payload instead)
157
+ # @return [Net::HTTPResponse] The HTTP response
158
+ # @example Update a resource
159
+ # client = Net::HTTP::Ext.new("https://api.example.com")
160
+ # response = client.put("/users/123", payload: {name: "John Updated"})
161
+ def put(path, headers: {}, params: nil, payload: nil)
162
+ request(:put, path, headers: headers, body: payload || params)
163
+ end
164
+
165
+ # @param path [String] The path to request
166
+ # @param headers [Hash] Additional headers for this request
167
+ # @param payload [Hash, String] Parameters to send as request body
168
+ # @param params [Hash] Parameters to send as query parameters (deprecated - use payload instead)
169
+ # @return [Net::HTTPResponse] The HTTP response
170
+ # @example Delete a resource
171
+ # client = Net::HTTP::Ext.new("https://api.example.com")
172
+ # response = client.delete("/users/123")
173
+ # response = client.delete("/users/123", payload: {confirm: true})
174
+ def delete(path, headers: {}, params: nil, payload: nil)
175
+ request(:delete, path, headers: headers, body: payload || params)
176
+ end
177
+
178
+ # @param path [String] The path to request
179
+ # @param headers [Hash] Additional headers for this request
180
+ # @param payload [Hash, String] Parameters to send as request body
181
+ # @param params [Hash] Parameters to send as query parameters (deprecated - use payload instead)
182
+ # @return [Net::HTTPResponse] The HTTP response
183
+ # @example Partially update a resource
184
+ # client = Net::HTTP::Ext.new("https://api.example.com")
185
+ # response = client.patch("/users/123", payload: {status: "inactive"})
186
+ def patch(path, headers: {}, params: nil, payload: nil)
187
+ request(:patch, path, headers: headers, body: payload || params)
188
+ end
189
+
190
+ # @param path [String] The path to request
191
+ # @param headers [Hash] Additional headers for this request
192
+ # @param params [Hash, String] Parameters to send with the request
193
+ # @return [Net::HTTPResponse] The HTTP response
194
+ # @example Make a simple GET request and automatically parse the JSON response
195
+ # client = Net::HTTP::Ext.new("https://api.example.com")
196
+ # response = client.get_json("/users")
197
+ def get_json(path, headers: {}, params: {})
198
+ response = get(path, headers: headers, params: params)
199
+ JSON.parse(response.body)
200
+ end
201
+
202
+ # Set or update default headers
203
+ #
204
+ # @param headers [Hash] Headers to set as default
205
+ def set_default_headers(headers)
206
+ @default_headers = normalize_headers(headers)
207
+ end
208
+
209
+ # Method to explicitly close all persistent connections
210
+ def close!
211
+ @http.shutdown
212
+ end
213
+
214
+ private
215
+
216
+ def create_logger
217
+ Logger.new($stdout, level: ENV.fetch("LOG_LEVEL", "INFO").upcase)
218
+ end
219
+
220
+ # Create a persistent HTTP client with configured timeouts and SSL settings
221
+ def create_http_client(options)
222
+ # Extract only the parameters accepted by Net::HTTP::Persistent.new
223
+ constructor_options = {}
224
+ constructor_options[:name] = options.delete(:name) if options.key?(:name)
225
+ constructor_options[:proxy] = options.delete(:proxy) if options.key?(:proxy)
226
+ constructor_options[:pool_size] = options.delete(:pool_size) if options.key?(:pool_size)
227
+
228
+ # Create the HTTP client with only the supported constructor options
229
+ http = Net::HTTP::Persistent.new(**constructor_options)
230
+
231
+ # Apply timeouts and other options as attributes after initialization
232
+ http.open_timeout = options[:open_timeout] if options.key?(:open_timeout)
233
+ http.read_timeout = options[:read_timeout] if options.key?(:read_timeout)
234
+ http.idle_timeout = options[:idle_timeout] if options.key?(:idle_timeout)
235
+
236
+ # Configure SSL if using HTTPS with safe defaults
237
+ configure_ssl(http, options)
238
+
239
+ # Apply any other options that might be supported as attributes
240
+ options.each do |key, value|
241
+ setter = "#{key}="
242
+ if http.respond_to?(setter)
243
+ http.send(setter, value) # rubocop:disable GitHub/AvoidObjectSendWithDynamicMethod
244
+ else
245
+ @log.debug("Ignoring unsupported option: #{key}")
246
+ end
247
+ end
248
+
249
+ http
250
+ end
251
+
252
+ # Normalize headers by converting keys to lowercase
253
+ #
254
+ # @param headers [Hash] Headers to normalize
255
+ # @return [Hash] Normalized headers with lowercase keys
256
+ def normalize_headers(headers)
257
+ return {} if headers.nil?
258
+
259
+ result = {}
260
+ headers.each do |key, value|
261
+ normalized_key = key.to_s.downcase
262
+ result[normalized_key] = value
263
+ end
264
+ result
265
+ end
266
+
267
+ # Build an HTTP request with proper headers and parameters
268
+ #
269
+ # @param method [Symbol] HTTP method (:get, :post, etc)
270
+ # @param path [String] Request path
271
+ # @param headers [Hash] Request headers
272
+ # @param params [Hash] Request parameters or body (optional)
273
+ # @return [Net::HTTP::Request] The prepared request object
274
+ def build_request(method, path, headers: {}, params: nil)
275
+ validate_querystring(path, params)
276
+
277
+ normalized_headers = prepare_headers(headers)
278
+ request = initialize_request(method, path, params, normalized_headers)
279
+
280
+ add_headers_to_request(request, normalized_headers)
281
+ request
282
+ end
283
+
284
+ def validate_querystring(path, params)
285
+ if path.include?("?") && params && !params.empty?
286
+ raise ArgumentError, "Querystring must be sent via `params` or `path` but not both."
287
+ end
288
+ end
289
+
290
+ def prepare_headers(headers)
291
+ normalized_headers = @default_headers.dup
292
+ normalize_headers(headers).each { |key, value| normalized_headers[key] = value }
293
+ validate_host_header(normalized_headers)
294
+ end
295
+
296
+ def initialize_request(method, path, params, headers)
297
+ case method
298
+ when :get, :head
299
+ full_path = encode_path_params(path, params)
300
+ VERB_MAP[method].new(full_path)
301
+ else
302
+ request = VERB_MAP[method].new(path)
303
+ set_request_body(request, params, headers)
304
+ request
305
+ end
306
+ end
307
+
308
+ def set_request_body(request, params, headers)
309
+ # Early return for nil or empty params
310
+ return if params.nil? || (params.respond_to?(:empty?) && params.empty?)
311
+
312
+ # normalize headers to an empty hash if nil
313
+ headers = {} if headers.nil?
314
+
315
+ begin
316
+ # First handle the case where params is already a string
317
+ if params.is_a?(String)
318
+ # Use the string directly
319
+ request.body = params
320
+ # Set content-type if not present (use lowercase for consistency)
321
+ headers["content-type"] ||= "application/octet-stream"
322
+ else
323
+ # Get content type, normalize by downcasing and trimming
324
+ # First, find the content-type key in a case-insensitive way
325
+ content_type_key = headers.keys.find { |k| k.to_s.downcase == "content-type" }
326
+ content_type = content_type_key ? headers[content_type_key].downcase.strip : nil
327
+
328
+ # Handle different content types for non-string params
329
+ request.body = case
330
+ # No content type specified - use JSON as default
331
+ when content_type.nil?
332
+ headers["content-type"] = "application/json"
333
+ serialize_to_json(params)
334
+
335
+ # Form URL-encoded content
336
+ when content_type.start_with?("application/x-www-form-urlencoded")
337
+ if params.respond_to?(:to_h)
338
+ URI.encode_www_form(params.to_h)
339
+ else
340
+ raise ArgumentError, "Parameters must be Hash-like for form URL-encoded requests, got #{params.class}"
341
+ end
342
+
343
+ # JSON content type
344
+ when content_type.start_with?("application/json")
345
+ serialize_to_json(params)
346
+
347
+ # Any other content type - use what's provided
348
+ else
349
+ # For other content types, use the provided format but log a warning
350
+ @log.debug("Unknown content-type: #{content_type}, attempting to serialize as JSON")
351
+ serialize_to_json(params)
352
+ end
353
+ end
354
+
355
+ # Set content length based on the body if not already set
356
+ request["Content-Length"] ||= request.body.bytesize.to_s if request.body
357
+ rescue => e
358
+ error_message = "Failed to set request body: #{e.message}"
359
+ @log.error(error_message)
360
+ raise ArgumentError, error_message
361
+ end
362
+ end
363
+
364
+ # Helper method to safely serialize objects to JSON
365
+ def serialize_to_json(obj)
366
+ begin
367
+ case obj
368
+ when Hash, Array
369
+ obj.to_json
370
+ when ->(o) { o.respond_to?(:to_h) }
371
+ obj.to_h.to_json
372
+ when ->(o) { o.respond_to?(:to_json) }
373
+ obj.to_json
374
+ else
375
+ raise ArgumentError, "Cannot convert #{obj.class} to JSON"
376
+ end
377
+ rescue JSON::GeneratorError => e
378
+ raise ArgumentError, "Invalid JSON data: #{e.message}"
379
+ end
380
+ end
381
+
382
+ def add_headers_to_request(request, headers)
383
+ headers.each { |key, value| request[key] = value }
384
+ end
385
+
386
+ def validate_host_header(normalized_headers)
387
+ # Validate the Host header
388
+ if normalized_headers["host"] && normalized_headers["host"] != @uri.host
389
+ raise ArgumentError,
390
+ "Host header does not match the request URI host: expected #{@uri.host}, got #{normalized_headers['host']}"
391
+ end
392
+
393
+ # Ensure the Host header is set to the URI's host if not explicitly provided
394
+ normalized_headers["host"] ||= @uri.host
395
+
396
+ return normalized_headers
397
+ end
398
+
399
+ # Execute an HTTP request with automatic retries on connection failures
400
+ #
401
+ # @param method [Symbol] HTTP method (:get, :post, etc)
402
+ # @param path [String] Request path
403
+ # @param headers [Hash] Request headers
404
+ # @param body [Hash] Request parameters or body (optional)
405
+ # @return [Net::HTTPResponse] The HTTP response
406
+ def request(method, path, headers: {}, body: nil)
407
+ req = build_request(method, path, headers: headers, params: body)
408
+ attempts = 0
409
+ retries = 0
410
+ start_time = Time.now
411
+
412
+ begin
413
+ attempts += 1
414
+ response = if @request_timeout
415
+ Timeout.timeout(@request_timeout) do
416
+ @http.request(@uri, req)
417
+ end
418
+ else
419
+ @http.request(@uri, req)
420
+ end
421
+
422
+ duration = Time.now - start_time
423
+ @log.debug("Request completed: method=#{method}, path=#{path}, status=#{response.code}, duration=#{format_duration_ms(duration)}")
424
+ response
425
+ rescue Timeout::Error => e
426
+ duration = Time.now - start_time
427
+ @log.error("Request timed out after #{format_duration_ms(duration)}: method=#{method}, path=#{path}")
428
+ raise
429
+ rescue Net::HTTP::Persistent::Error, Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNRESET => e
430
+ retries += 1
431
+ if retries <= @max_retries
432
+ @log.debug("Connection failed: #{e.message} - rebuilding HTTP client (retry #{retries}/#{@max_retries})")
433
+ @http = create_http_client
434
+ retry
435
+ else
436
+ duration = Time.now - start_time
437
+ @log.error("Connection failed after #{retries - 1} retries (#{format_duration_ms(duration)}): #{e.message}")
438
+ raise
439
+ end
440
+ end
441
+ end
442
+
443
+ def configure_ssl(http, options)
444
+ return unless @uri.scheme == "https"
445
+
446
+ http.verify_mode = options.fetch(:verify_mode, OpenSSL::SSL::VERIFY_PEER)
447
+ http.verify_hostname = options.fetch(:verify_hostname, true) if http.respond_to?(:verify_hostname=)
448
+ http.ssl_version = options.fetch(:ssl_version, :TLSv1_2)
449
+ http.ca_file = options.fetch(:ca_file, @ssl_cert_file) if options.fetch(:ca_file, @ssl_cert_file)
450
+ end
451
+
452
+ # Format duration in milliseconds
453
+ #
454
+ # @param duration [Float] Duration in seconds
455
+ # @return [String] Formatted duration in milliseconds
456
+ def format_duration_ms(duration)
457
+ "#{(duration * 1000).round(2)} ms"
458
+ end
459
+
460
+ # Encode path parameters into a URL query string
461
+ #
462
+ # @param path [String] The base path
463
+ # @param params [Hash] Parameters to encode
464
+ # @return [String] The path with encoded parameters
465
+ def encode_path_params(path, params)
466
+ return path if params.nil? || params.empty?
467
+
468
+ encoded = URI.encode_www_form(params)
469
+ [path, encoded].join("?")
470
+ end
471
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NetHTTPExt
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/net/http/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "net-http-ext"
7
+ spec.version = NetHTTPExt::VERSION
8
+ spec.authors = ["Grant Birkinbine"]
9
+ spec.email = "grant.birkinbine@gmail.com"
10
+ spec.license = "MIT"
11
+
12
+ spec.summary = "Ruby Net::HTTP extended with sensible defaults"
13
+ spec.description = <<~SPEC_DESC
14
+ Safe defaults, persistent connections, thread safety, and basic logging for Ruby's Net::HTTP library
15
+ SPEC_DESC
16
+
17
+ spec.homepage = "https://github.com/grantbirki/net-http-ext"
18
+ spec.metadata = {
19
+ "source_code_uri" => "https://github.com/grantbirki/net-http-ext",
20
+ "documentation_uri" => "https://github.com/grantbirki/net-http-ext",
21
+ "bug_tracker_uri" => "https://github.com/grantbirki/net-http-ext/issues"
22
+ }
23
+
24
+ spec.add_dependency "timeout", "~> 0.4.3"
25
+ spec.add_dependency "json", "~> 2.10", ">= 2.10.2"
26
+ spec.add_dependency "uri", "~> 1.0", ">= 1.0.3"
27
+ spec.add_dependency "logger", "~> 1"
28
+ spec.add_dependency "net-http-persistent", "~> 4.0", ">= 4.0.5"
29
+
30
+ # https://github.com/drbrain/net-http-persistent/blob/234f3b2c6a0ed044e3c55e3de982257b4860ba0a/net-http-persistent.gemspec#L17C29-L17C37
31
+ spec.required_ruby_version = ">= 2.4"
32
+
33
+ spec.files = %w[LICENSE README.md net-http-ext.gemspec]
34
+ spec.files += Dir.glob("lib/**/*.rb")
35
+ spec.require_paths = ["lib"]
36
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: net-http-ext
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Grant Birkinbine
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-04-11 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: timeout
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.4.3
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.4.3
26
+ - !ruby/object:Gem::Dependency
27
+ name: json
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.10'
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 2.10.2
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '2.10'
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 2.10.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: uri
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '1.0'
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 1.0.3
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '1.0'
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: 1.0.3
66
+ - !ruby/object:Gem::Dependency
67
+ name: logger
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - "~>"
71
+ - !ruby/object:Gem::Version
72
+ version: '1'
73
+ type: :runtime
74
+ prerelease: false
75
+ version_requirements: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '1'
80
+ - !ruby/object:Gem::Dependency
81
+ name: net-http-persistent
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '4.0'
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 4.0.5
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.0'
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 4.0.5
100
+ description: 'Safe defaults, persistent connections, thread safety, and basic logging
101
+ for Ruby''s Net::HTTP library
102
+
103
+ '
104
+ email: grant.birkinbine@gmail.com
105
+ executables: []
106
+ extensions: []
107
+ extra_rdoc_files: []
108
+ files:
109
+ - LICENSE
110
+ - README.md
111
+ - lib/net/http/ext.rb
112
+ - lib/net/http/version.rb
113
+ - net-http-ext.gemspec
114
+ homepage: https://github.com/grantbirki/net-http-ext
115
+ licenses:
116
+ - MIT
117
+ metadata:
118
+ source_code_uri: https://github.com/grantbirki/net-http-ext
119
+ documentation_uri: https://github.com/grantbirki/net-http-ext
120
+ bug_tracker_uri: https://github.com/grantbirki/net-http-ext/issues
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '2.4'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubygems_version: 3.6.2
136
+ specification_version: 4
137
+ summary: Ruby Net::HTTP extended with sensible defaults
138
+ test_files: []