summaryrefslogtreecommitdiff
path: root/lib/net/hippie/connection.rb
blob: 465461ff175c9be6bf0c78c4fbbf5040062e306c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# frozen_string_literal: true

module Net
  module Hippie
    # Persistent HTTP connection with automatic reconnection.
    class Connection
      RETRYABLE_ERRORS = [EOFError, Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError].freeze

      def initialize(scheme, host, port, ipaddr, options = {})
        @mutex = Mutex.new
        @created_at = Time.now
        @http = build_http(scheme, host, port, ipaddr, options)
      end

      def run(request, &block)
        @mutex.synchronize do
          ensure_started
          execute(request, &block)
        end
      end

      def stale?(ttl)
        (Time.now - @created_at) > ttl
      end

      def close
        @mutex.synchronize { @http.finish if @http.started? }
      rescue IOError
        nil
      end

      def build_url_for(path)
        return path if path.start_with?('http')

        "#{@http.use_ssl? ? 'https' : 'http'}://#{@http.address}#{path}"
      end

      private

      def build_http(scheme, host, port, ipaddr, options)
        Net::HTTP.new(host, port).tap do |http|
          configure_timeouts(http, options)
          configure_ssl(http, scheme, options)
          configure_tls_client(http, options)
          http.ipaddr = ipaddr if ipaddr
        end
      end

      def configure_timeouts(http, options)
        http.open_timeout = options.fetch(:open_timeout, 10)
        http.read_timeout = options.fetch(:read_timeout, 10)
        http.write_timeout = options.fetch(:write_timeout, 10)
        http.keep_alive_timeout = options.fetch(:keep_alive_timeout, 30)
        http.max_retries = options.fetch(:max_retries, 1)
        http.continue_timeout = options[:continue_timeout] if options[:continue_timeout]
        http.ignore_eof = options.fetch(:ignore_eof, true)
      end

      def configure_ssl(http, scheme, options)
        http.use_ssl = scheme == 'https'
        return unless http.use_ssl?

        http.min_version = options.fetch(:min_version, :TLS1_2)
        http.verify_mode = options.fetch(:verify_mode, Net::Hippie.verify_mode)
        http.set_debug_output(options[:logger]) if options[:logger]
      end

      def configure_tls_client(http, options)
        http.cert = options[:tls_cert] if options[:tls_cert]
        http.key = options[:tls_key] if options[:tls_key]
      end

      def ensure_started
        @http.start unless @http.started?
      end

      def execute(request, retried: false, &block)
        block ? @http.request(request, &block) : @http.request(request)
      rescue *RETRYABLE_ERRORS
        raise if retried

        @http.finish if @http.started?
        @http.start
        execute(request, retried: true, &block)
      end
    end
  end
end