main
 1# frozen_string_literal: true
 2
 3require 'base64'
 4require 'json'
 5require 'logger'
 6require 'monitor'
 7require 'net/http'
 8require 'openssl'
 9require 'resolv'
10require 'timeout'
11
12require 'net/hippie/version'
13require 'net/hippie/client'
14require 'net/hippie/connection'
15require 'net/hippie/connection_pool'
16require 'net/hippie/content_type_mapper'
17
18module Net
19  # High-performance HTTP client with connection pooling and DNS caching.
20  module Hippie
21    CONNECTION_ERRORS = [
22      EOFError,
23      Errno::ECONNREFUSED,
24      Errno::ECONNRESET,
25      Errno::EHOSTUNREACH,
26      Errno::EINVAL,
27      Errno::EPIPE,
28      Errno::ECONNABORTED,
29      Errno::ETIMEDOUT,
30      IOError,
31      Net::OpenTimeout,
32      Net::ProtocolError,
33      Net::ReadTimeout,
34      Net::WriteTimeout,
35      OpenSSL::OpenSSLError,
36      OpenSSL::SSL::SSLError,
37      SocketError,
38      Timeout::Error
39    ].freeze
40
41    BASIC_PREFIX = 'Basic '
42    BEARER_PREFIX = 'Bearer '
43
44    class << self
45      attr_writer :logger, :verify_mode
46
47      def logger
48        @logger ||= Logger.new(nil)
49      end
50
51      def verify_mode
52        @verify_mode ||= OpenSSL::SSL::VERIFY_PEER
53      end
54
55      def basic_auth(username, password)
56        BASIC_PREFIX + ::Base64.strict_encode64("#{username}:#{password}")
57      end
58
59      def bearer_auth(token)
60        BEARER_PREFIX + token
61      end
62
63      def default_client
64        @default_client ||= Client.new(follow_redirects: 3, logger: logger)
65      end
66
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
76
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
83    end
84  end
85end