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