main
 1# frozen_string_literal: true
 2
 3module Net
 4  module Hippie
 5    # Persistent HTTP connection with automatic reconnection.
 6    class Connection
 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)
13      end
14
15      def run(request, &block)
16        @mutex.synchronize do
17          ensure_started
18          execute(request, &block)
19        end
20      end
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
32      def build_url_for(path)
33        return path if path.start_with?('http')
34
35        "#{@http.use_ssl? ? 'https' : 'http'}://#{@http.address}#{path}"
36      end
37
38      private
39
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
48
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]
71      end
72
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)
85      end
86    end
87  end
88end