dialed 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: 6d782b0da7b47eb1f23159a03c8ff84b96f619641586f4d4e63c344912d84d06
4
+ data.tar.gz: c83dc4502bb902e2d7dc0103d27c0dea56f32c2ec324b0eeb993bbe4744b28b9
5
+ SHA512:
6
+ metadata.gz: cc6952bb3d6f5119b9e1c9288c7ad86b5f63db88e4a634749091c1473c33b9a762b9c8a7db5ed5b5b692f0acfcfbaf757362de9fc89837afc6c9141f79d39cb2
7
+ data.tar.gz: e90af846eabeebcdda0d820691faa64bf47fd6cfa4f35c83f275fa151f45855b50007a4f469777f811e1d5138f59d29956aa77d64ac0dd8be2f154cf25231e51
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class Client
6
+ def self.build(&block)
7
+ ExplicitClient.new create_connection_builder(&block)
8
+ end
9
+
10
+ def self.create_connection_builder(&block)
11
+ if block_given?
12
+ connection_builder = ConnectionBuilder.new
13
+ block.call(connection_builder)
14
+ return connection_builder
15
+ end
16
+ ConnectionBuilder.apply_defaults
17
+ end
18
+
19
+ def close
20
+ return if @closed
21
+
22
+ @closed = true
23
+ with_dialer(&:hangup!)
24
+ end
25
+
26
+ attr_accessor :waiter
27
+
28
+ def initialize(connection_builder = ConnectionBuilder.apply_defaults)
29
+ @connection_builder = connection_builder
30
+ @async_task_count = 0
31
+ @closed = false
32
+ end
33
+
34
+ def get(location, query: {}, **kwargs)
35
+ with_dialer do |dialer|
36
+ response = dialer.call('GET', location, **kwargs)
37
+ response
38
+ end
39
+ end
40
+
41
+ def async(&block)
42
+ Async do |task|
43
+ waiter = Async::Waiter.new(parent: task)
44
+ waiting_client = dup
45
+ waiting_client.waiter = waiter
46
+ arr = []
47
+ implicit = block.call(waiting_client, arr)
48
+ if arr.empty?
49
+ waiter.wait(waiter.instance_variable_get(:@done).count)
50
+ implicit
51
+ else
52
+ enum = Enumerator::Lazy.new(arr) do |yielder, *values|
53
+ if values.size == 1
54
+ value = values.first
55
+ value.wait
56
+ yielder << value.result
57
+ else
58
+ values.each(&:wait)
59
+ yielder << values.map(&:result)
60
+ end
61
+ end
62
+ enum
63
+ end
64
+ end
65
+ end
66
+
67
+ attr_reader :connection_builder
68
+
69
+ def with_dialer(&block)
70
+ if waiter
71
+ waiter.async do
72
+ fetch_dialer(&block)
73
+ end
74
+ elsif Async::Task.current?
75
+ fetch_dialer do |dialer|
76
+ block.call(dialer)
77
+ end
78
+ else
79
+ Sync do
80
+ fetch_dialer do |dialer|
81
+ dialer.start_session do |session|
82
+ block.call(session)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class Connection
6
+ attr_reader :configuration
7
+
8
+ delegate :ssl_context, to: :configuration
9
+ delegate :uri, :version, to: :configuration, prefix: :remote
10
+ delegate :host, :port, to: :remote_host
11
+ delegate :authority, :scheme, to: :remote_uri
12
+
13
+ delegate :version, :http2?, :http1?, to: :internal_connection
14
+ delegate :call, to: :internal_connection
15
+
16
+ alias remote_host remote_uri
17
+
18
+ def initialize(configuration)
19
+ @semaphore = Async::Semaphore.new
20
+ # @semaphore2 = Async::Semaphore.new
21
+ @configuration = configuration
22
+ end
23
+
24
+ def ping
25
+ internal_connection.send_ping(SecureRandom.bytes(8))
26
+ end
27
+
28
+ def address
29
+ "#{host}:#{port}"
30
+ end
31
+
32
+ def closed?
33
+ internal_connection.closed?
34
+ end
35
+
36
+ def open?
37
+ !closed?
38
+ end
39
+
40
+ # def call(...)
41
+ # Sync do
42
+ # @semaphore2.acquire do
43
+ # Sync do
44
+ # reconnect! if closed?
45
+ # end
46
+ # internal_connection.call(...)
47
+ # end
48
+ # end
49
+ # end
50
+
51
+ def connect
52
+ open?
53
+ # !!internal_connection
54
+ rescue StandardError => e
55
+ @internal_connection = NilConnection.new
56
+ raise e
57
+ end
58
+
59
+ def nil_connection?
60
+ false
61
+ end
62
+
63
+ def reconnect!
64
+ @semaphore.acquire do
65
+ @internal_connection = create_internal_connection
66
+ end
67
+ end
68
+
69
+ def close
70
+ raise NotImplementedError, 'Subclasses must implement close'
71
+ end
72
+
73
+ protected
74
+
75
+ def create_internal_connection
76
+ raise NotImplementedError, 'Subclasses must implement create_internal_connection'
77
+ end
78
+
79
+ private
80
+
81
+ # A semaphore is used for now to prevent a stampede of opening new connections if multiple
82
+ # requests are being made concurrently and the connection does not yet exist. Unclear if this is needed
83
+ # after the connection has been created and/or if async-http handles its own isolation
84
+ def internal_connection
85
+ @semaphore.acquire do
86
+ __fetch_internal_connection
87
+ end
88
+ end
89
+
90
+ def __fetch_internal_connection
91
+ @internal_connection = create_internal_connection if needs_new_connection?
92
+ @internal_connection
93
+ end
94
+
95
+ # Instead, use the instance variable directly:
96
+ def needs_new_connection?
97
+ @internal_connection.nil? || @internal_connection.closed? # Correct - Checks raw state
98
+ end
99
+
100
+ def async_http_protocol
101
+ case remote_version
102
+ in :h2 then Async::HTTP::Protocol::HTTP2
103
+ in :http11 | :http10 | :http1 then Async::HTTP::Protocol::HTTP1
104
+ else raise "Unsupported protocol: #{remote_version}. Must be either :h2 or :http11 or :http1 or :http10"
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class ConnectionBuilder
6
+ DirectConnectionConfiguration = Data.define(:uri, :version, :ssl_context)
7
+ TunneledConnectionConfiguration = Data.define(:uri, :proxy_uri, :version, :ssl_context)
8
+ using Dialed::Refinements::Presence
9
+
10
+ attr_accessor :ssl_context
11
+ attr_reader :version, :scheme, :uri, :proxy_uri
12
+
13
+ delegate :host, :port, :scheme, to: :uri
14
+ delegate :host=, :port=, to: :uri
15
+
16
+ def self.apply_defaults
17
+ new.tap(&:apply_defaults!)
18
+ end
19
+
20
+ def initialize
21
+ @version = :h2
22
+ @proxy_uri = nil
23
+ @ssl_context = build_ssl_context
24
+ @uri = Addressable::URI.new
25
+ @uri_defaults = { scheme: 'https', port: 443 }
26
+ end
27
+
28
+ def build_ssl_context
29
+ OpenSSL::SSL::SSLContext.new.tap do |ssl_context|
30
+ ssl_context.alpn_protocols = %w[h2 http/1.1]
31
+ ssl_context.verify_hostname = true
32
+ end
33
+ end
34
+
35
+ def scheme=(scheme)
36
+ uri.scheme = scheme
37
+ case scheme
38
+ in 'http'
39
+ uri.port = 80
40
+ in 'https'
41
+ uri.port = 443
42
+ else
43
+ raise ArgumentError, "Unsupported scheme: #{scheme.inspect}"
44
+ end
45
+ end
46
+
47
+ def uri_valid?
48
+ uri.send(:validate).nil?
49
+ end
50
+
51
+ def valid?
52
+ uri_valid? && !ssl_context.nil? && version.present?
53
+ end
54
+
55
+ def build
56
+ apply_defaults!
57
+ raise Dialed::Error, 'Cannot build. Invalid' unless valid?
58
+
59
+ if proxy_uri
60
+ configuration = TunneledConnectionConfiguration.new(
61
+ uri: uri,
62
+ version: version,
63
+ ssl_context: ssl_context,
64
+ proxy_uri: proxy_uri
65
+ )
66
+ TunneledConnection.new(configuration)
67
+ else
68
+ configuration = DirectConnectionConfiguration.new(
69
+ uri: uri,
70
+ version: version,
71
+ ssl_context: ssl_context
72
+ )
73
+ DirectConnection.new(configuration)
74
+ end
75
+ end
76
+
77
+ def cert_store
78
+ ssl_context.cert_store ||= OpenSSL::X509::Store.new
79
+ end
80
+
81
+ def alpn_protocols=(protocols)
82
+ ssl_context.alpn_protocols = protocols
83
+ end
84
+
85
+ def alpn_protocols
86
+ ssl_context.alpn_protocols
87
+ end
88
+
89
+ def cert_store=(cert_store)
90
+ ssl_context.cert_store = cert_store
91
+ end
92
+
93
+ def add_certificate(path)
94
+ pathname = Pathname(path)
95
+ pathname = Pathname(File.expand_path(path)) if pathname.relative?
96
+
97
+ certificate = OpenSSL::X509::Certificate.new(pathname.read)
98
+ cert_store.add_cert(certificate)
99
+ self
100
+ end
101
+
102
+ def version=(version)
103
+ version = version.to_s
104
+
105
+ case version
106
+ in '1.0' | '10' | 1 | 'http/1.0' | 'HTTP/1.0'
107
+ @version = :http10
108
+ self.alpn_protocols = ['http/1.0']
109
+ in '1.1' | '11' | 'http/1.1' | 'HTTP/1.1'
110
+ @version = :http11
111
+ self.alpn_protocols = ['http/1.1']
112
+ in '2.0' | '20' | 2 | 'http/2' | 'HTTP/2'
113
+ @version = :h2
114
+ self.alpn_protocols = ['h2']
115
+ else
116
+ raise ArgumentError, "Unsupported HTTP version: #{version.inspect}"
117
+ end
118
+ end
119
+
120
+ def verify_peer=(verify_peer)
121
+ ssl_context.verify_mode = (OpenSSL::SSL::VERIFY_PEER if verify_peer)
122
+ end
123
+
124
+ def verify_none=(verify_none)
125
+ ssl_context.verify_mode = (OpenSSL::SSL::VERIFY_NONE if verify_none)
126
+ end
127
+
128
+ alias insecure= verify_none=
129
+
130
+ def uri=(uri)
131
+ input = Addressable::URI.parse(uri)
132
+ input_no_path = input.dup
133
+ input_no_path.path = nil
134
+ input_no_path.query = nil
135
+ input_no_path.fragment = nil
136
+ @uri = input_no_path
137
+ end
138
+
139
+ def proxy(&block)
140
+ if block_given?
141
+ uri = ProxyUri.new.tap(&block)
142
+ uri.infer_scheme_if_missing!
143
+ raise ArgumentError, "Invalid proxy URI: #{uri.inspect}" unless uri.valid?
144
+
145
+ @proxy_uri = uri
146
+
147
+ self
148
+ else
149
+ @proxy_uri
150
+ end
151
+ self
152
+ end
153
+
154
+ def proxy=(proxy_uri)
155
+ parsed = Addressable::URI.parse(proxy_uri)
156
+ proxy do |self_proxy|
157
+ self_proxy.merge!(parsed)
158
+ end
159
+ end
160
+
161
+ alias proxy_uri= proxy=
162
+
163
+ def apply_defaults!
164
+ defaults_to_apply = @uri_defaults.select do |key, _value|
165
+ uri_value = uri.send(key)
166
+ next if uri_value.present?
167
+
168
+ true
169
+ end
170
+
171
+ uri.merge!(defaults_to_apply)
172
+ self
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class Dialer
6
+ attr_reader :connection
7
+
8
+ def initialize(builder = ConnectionBuilder.apply_defaults, lazy: true, &block)
9
+ @builder = builder
10
+ @connection = NilConnection.new
11
+ @lazy = lazy
12
+ start_session(&block) if block_given?
13
+ end
14
+
15
+ def connected?
16
+ connection.open?
17
+ end
18
+
19
+ def lazy?
20
+ @lazy
21
+ end
22
+
23
+ def disconnected?
24
+ connection.closed?
25
+ end
26
+
27
+ def current_host
28
+ return nil unless on_a_call?
29
+
30
+ connection.remote_host
31
+ end
32
+
33
+ def start_session(&block)
34
+ attempt_connection!
35
+ block.call(self)
36
+ ensure
37
+ hangup!
38
+ end
39
+
40
+ def call(verb, location, *args, proxy_uri: nil, **kwargs)
41
+ location_uri = Addressable::URI.parse(location)
42
+ request = Request.new(verb, location_uri.path, *args, **kwargs)
43
+ response = (
44
+ if connection.open?
45
+ response = request.call(connection)
46
+ Response.new(response)
47
+ elsif lazy?
48
+ @builder.uri = location_uri
49
+ @builder.proxy_uri = proxy_uri if proxy_uri
50
+ success = attempt_connection!
51
+ raise Dialed::Error, "Failed to connect to #{location}. connection status: #{connection.open?}" unless success
52
+
53
+ Response.new(request.call(connection))
54
+ else
55
+ success = attempt_connection!
56
+ raise Dialed::Error, "Failed to connect to #{location}. connection status: #{connection.open?}" unless success
57
+
58
+ Response.new(request.call(connection))
59
+ end
60
+
61
+ )
62
+
63
+ return response unless block_given?
64
+
65
+ begin
66
+ yield response
67
+ ensure
68
+ response.close
69
+ end
70
+ end
71
+
72
+ def hangup!
73
+ connection.close if connection.open?
74
+ @connection = NilConnection.new
75
+ end
76
+
77
+ def ready?
78
+ raise 'Expected connection not to be actually nil' if connection.nil?
79
+ return false if connection.nil_connection?
80
+ return false if connection.open?
81
+ return false unless @builder.valid?
82
+
83
+ true
84
+ end
85
+
86
+ private
87
+
88
+ def attempt_connection!
89
+ return true if ready?
90
+
91
+ @connection = @builder.build
92
+ connection.connect
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class DirectConnection < Connection
6
+ def close
7
+ internal_connection.close
8
+ end
9
+
10
+ protected
11
+
12
+ def create_internal_connection
13
+ remote_endpoint = Async::HTTP::Endpoint.parse(
14
+ remote_uri.to_s,
15
+ protocol: async_http_protocol,
16
+ ssl_context: ssl_context,
17
+ alpn_protocols: ssl_context.alpn_protocols
18
+ )
19
+
20
+ remote_sock = remote_endpoint.connect
21
+ async_http_protocol.client(remote_sock)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class ExplicitClient < Client
6
+ def initialize(connection_builder)
7
+ super
8
+ @dialer = Dialer.new(connection_builder, lazy: false)
9
+ end
10
+
11
+ protected
12
+
13
+ def fetch_dialer(&block)
14
+ block.call(@dialer)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class NilConnection < Connection
6
+ def initialize
7
+ super(uri: nil, version: nil, ssl_context: nil)
8
+ end
9
+
10
+ def remote_host
11
+ nil
12
+ end
13
+
14
+ def remote_uri
15
+ nil
16
+ end
17
+
18
+ def ssl_context
19
+ nil
20
+ end
21
+
22
+ def http2?
23
+ false
24
+ end
25
+
26
+ def http1?
27
+ false
28
+ end
29
+
30
+ def open?
31
+ false
32
+ end
33
+
34
+ def nil_connection?
35
+ true
36
+ end
37
+
38
+ def closed?
39
+ true
40
+ end
41
+
42
+ private
43
+
44
+ def internal_connection
45
+ Class.new do
46
+ def call(...)
47
+ raise Dialed::Net::HTTP::ConnectionError, 'Tried to call a nil connection'
48
+ end
49
+ end.new
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class Operator
6
+ include Singleton
7
+
8
+ def initialize
9
+ @dialers = {}
10
+ end
11
+
12
+ def request_call(&block)
13
+ connection_builder = block.call(ConnectionBuilder.new) if block_given?
14
+ connection_builder ||= ConnectionBuilder.apply_defaults
15
+ end
16
+
17
+ def singleplex_dialers
18
+ @dialers
19
+ .reject { |_, dialer| dialer.disconnected? }
20
+ .select { |_, dialer| dialer.singleplex? }
21
+ end
22
+
23
+ def multiplex_dialers
24
+ @dialers.reject { |_, dialer| dialer.disconnected? }
25
+ .select { |_, dialer| dialer.multiplex? }
26
+ end
27
+
28
+ def checkout_dialer(connection_builder, &block)
29
+ full_connection_uri = connection_builder.full_connection_uri
30
+ plex_type = connection_builder.plex_type
31
+ case [full_connection_uri, plex_type, registry.keys]
32
+ in [URI => uri, :h2, [*, ^uri, *]]
33
+ block.call multiplex_dialers.fetch(uri)
34
+ in [URI => uri, :h1 | :h11, [*, ^uri, *]]
35
+ # not thread safe. Use async gem as it is not implemented with threads
36
+ dialer = remove_dialer(uri)
37
+ raise Dialed::Error, 'Dialer not found when it was expected to be. Is it possible you are using multiple threads?' unless dialer
38
+
39
+ block.call dialer
40
+ register_dialer dialer
41
+ in [URI => uri, Symbol, Array]
42
+ register_dialer dialer
43
+ block.call dialer
44
+ else
45
+ raise Dialed::Error, "Unknown Dialer type: #{full_connection_uri}, #{plex_type}"
46
+ end
47
+ end
48
+
49
+ def fetch_dialer(_uri, &)
50
+ Dialer.new(connection_builder, &)
51
+ end
52
+
53
+ def remove_dialer(dialer)
54
+ @dialers.delete(dialer.full_connection_uri)
55
+ end
56
+
57
+ def register_dialer(dialer)
58
+ @dialers[dialer.full_connection_uri] = dialer
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class ProxyUri < SimpleDelegator
6
+ using Dialed::Refinements::Presence
7
+
8
+ def self.parse(string)
9
+ new(Addressable::URI.parse(string))
10
+ end
11
+
12
+ def initialize(uri = Addressable::URI.parse('http://invalid.invalid'))
13
+ super
14
+ end
15
+
16
+ def infer_scheme_if_missing!
17
+ self.scheme ||= 'http'
18
+ end
19
+
20
+ def valid?
21
+ host.present? &&
22
+ port.present? &&
23
+ scheme.present?
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ module Dialed
2
+ module HTTP
3
+ class Request
4
+ attr_reader :verb, :path, :args, :options
5
+
6
+ def initialize(verb, path, *args, **options)
7
+ @verb = verb
8
+ @path = path
9
+ @args = args
10
+ @options = options
11
+ end
12
+
13
+ def call(connection)
14
+ # protocol_request = Protocol::HTTP::Request[
15
+ # verb,
16
+ # path,
17
+ # *args,
18
+ # version: connection.version,
19
+ # headers: options[:headers],
20
+ # method: verb.upcase,
21
+ # authority: connection.authority,
22
+ # scheme: connection.scheme,
23
+ # ]
24
+ protocol_request = Protocol::HTTP::Request.new.tap do |r|
25
+ r.path = path
26
+ r.method = verb.upcase
27
+ r.headers = options[:headers] if options[:headers]
28
+ r.version = connection.version
29
+ r.authority = connection.authority
30
+ r.scheme = connection.scheme
31
+ r.body = options[:body]
32
+ r.protocol = options[:protocol] if options[:protocol]
33
+ end
34
+
35
+ protocol_request.call(connection)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class Response::Body
6
+ def initialize(internal_body)
7
+ @internal_body = internal_body
8
+ @file = nil
9
+ end
10
+
11
+ def read
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def to_io
16
+ @to_io ||= begin
17
+ file = nil
18
+ begin
19
+ file = ::Tempfile.create(anonymous: true)
20
+ internal_body.each do |chunk|
21
+ file.write(chunk)
22
+ end
23
+ file.rewind
24
+ file
25
+ rescue => e
26
+ file.close
27
+ raise e
28
+ end
29
+ end
30
+ end
31
+
32
+ def http2?; end
33
+
34
+ private
35
+
36
+ def buffered_internal_body
37
+ @buffered_internal_body ||= begin
38
+ if @file
39
+ return to_io.tap(&:rewind)
40
+ .read
41
+ end
42
+ internal_body.read
43
+ end
44
+ end
45
+
46
+ def internal_body
47
+ if @internal_body.respond_to?(:rewindable?) && @internal_body.rewindable?
48
+ @internal_body
49
+ .rewind
50
+ # elsif @internal_body.is_a?(Async::HTTP::Protocol::HTTP2::Input)
51
+ elsif @internal_body.is_a?(Protocol::HTTP::Body::Buffered)
52
+ @internal_body.rewind
53
+ # buffered_body = @internal_body.finish
54
+ # buffered_body.rewind
55
+ end
56
+ @internal_body
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class Response::EveryBody < Response::Body
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,28 @@
1
+ module Dialed
2
+ module HTTP
3
+ class Response::JsonBody < Response::Body
4
+ alias to_json to_s
5
+
6
+ def read
7
+ # already memoized
8
+ buffered_internal_body
9
+ end
10
+
11
+ def to_s
12
+ read.to_s
13
+ end
14
+
15
+ def to_h
16
+ @__to_h ||= JSON.parse(read, symbolize_names: true)
17
+ end
18
+
19
+ def as_json
20
+ @__as_json ||= JSON.parse(read, symbolize_names: false)
21
+ end
22
+
23
+ def deconstruct_keys(keys)
24
+ keys ? to_h.slice(*keys) : to_h
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'async/http'
4
+ ::Async::HTTP::Body::Reader.module_eval do
5
+ def buffered!
6
+ if @body
7
+ @body = @body.finish
8
+ end
9
+
10
+ return self
11
+ end
12
+ end
13
+
14
+ module Dialed
15
+ module HTTP
16
+ class Response
17
+ delegate :to_io, :read, :to_h, :to_s, to: :body
18
+
19
+ def initialize(internal_response)
20
+ @internal_response = internal_response
21
+ @notifier = Async::Notification.new
22
+ buffer!
23
+ end
24
+
25
+ def body
26
+ @body ||= body_klass.new(internal_response.body)
27
+ end
28
+
29
+ def body_klass
30
+ case headers
31
+ in { 'content-type': 'application/json' }
32
+ JsonBody
33
+ else
34
+ EveryBody
35
+ end
36
+ end
37
+
38
+ def buffer!
39
+ @internal_response.buffered!
40
+ end
41
+
42
+ def http2?
43
+ internal_response.version == 'HTTP/2'
44
+ end
45
+
46
+ def http11?
47
+ internal_response.version == 'HTTP/1.1'
48
+ end
49
+
50
+ def headers
51
+ @headers ||= internal_response
52
+ &.headers
53
+ &.to_h
54
+ &.transform_keys(&:to_sym)
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :internal_response
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ module HTTP
5
+ class TunneledConnection < Connection
6
+ ProxyConnectError = Class.new(StandardError)
7
+
8
+ delegate :proxy_uri, to: :configuration
9
+
10
+ def close
11
+ internal_connection.close
12
+ end
13
+
14
+ protected
15
+
16
+ def create_internal_connection
17
+ proxy_connection = create_proxy_connection
18
+ remote_endpoint = Async::HTTP::Endpoint.parse(
19
+ remote_uri.to_s,
20
+ protocol: async_http_protocol,
21
+ ssl_context: ssl_context,
22
+ alpn_protocols: ssl_context.alpn_protocols
23
+ )
24
+
25
+ proxy = Async::HTTP::Proxy.new(proxy_connection, address)
26
+ proxied_endpoint = proxy.wrap_endpoint(remote_endpoint)
27
+
28
+ proxied_sock = (
29
+ begin
30
+ proxied_endpoint.connect
31
+ rescue Errno::ECONNRESET => e
32
+ proxy_connection.close
33
+ raise ProxyConnectError, e
34
+ end
35
+ )
36
+ async_http_protocol.client(proxied_sock)
37
+ end
38
+
39
+ private
40
+
41
+ def create_proxy_connection
42
+ proxy_endpoint = Async::HTTP::Endpoint.parse(proxy_uri.to_s)
43
+ Async::HTTP::Client.new(proxy_endpoint)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,59 @@
1
+ module Dialed
2
+ module Refinements
3
+ module PresenceMethods
4
+ def present?
5
+ !nil? && !empty?
6
+ end
7
+
8
+ def presence
9
+ self if present?
10
+ end
11
+ end
12
+
13
+ module Presence
14
+ refine NilClass do
15
+ def present?
16
+ false
17
+ end
18
+ end
19
+
20
+ refine String do
21
+ def present?
22
+ !nil? && !empty?
23
+ end
24
+ end
25
+
26
+ refine Integer do
27
+ def present?
28
+ !nil?
29
+ end
30
+ end
31
+
32
+ refine Array do
33
+ def present?
34
+ !nil? && !empty?
35
+ end
36
+
37
+ def presence
38
+ self if present?
39
+ end
40
+ end
41
+
42
+ refine Hash do
43
+ def present?
44
+ !nil? && !empty?
45
+ end
46
+
47
+ def presence
48
+ self if present?
49
+ end
50
+ end
51
+
52
+ refine Symbol do
53
+ def present?
54
+ !nil? && !empty?
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dialed
4
+ VERSION = '0.0.1'
5
+ end
data/lib/dialed.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'backports/3.2.0/data'
5
+ require 'zeitwerk'
6
+
7
+
8
+ autoload :Pathname, 'pathname'
9
+ autoload :Async, 'async'
10
+ autoload :Tempfile, 'tempfile'
11
+ autoload :Open3, 'open3'
12
+ autoload :OpenSSL, 'openssl'
13
+ autoload :Benchmark, 'benchmark'
14
+ autoload :Base64, 'base64'
15
+ autoload :SimpleDelegator, 'delegate'
16
+
17
+ require 'active_support/core_ext/module/delegation'
18
+
19
+ module Addressable
20
+ autoload :URI, 'addressable/uri'
21
+ end
22
+
23
+ module Async
24
+ autoload :Barrier, 'async/barrier'
25
+ autoload :Semaphore, 'async/semaphore'
26
+ autoload :HTTP, 'async/http'
27
+ autoload :Waiter, 'async/waiter'
28
+
29
+ module HTTP
30
+ autoload :Client, 'async/http/client.rb'
31
+ autoload :Proxy, 'async/http/proxy.rb'
32
+ autoload :Endpoint, 'async/http/endpoint.rb'
33
+ end
34
+ end
35
+
36
+ loader = Zeitwerk::Loader.for_gem
37
+ loader.inflector.inflect(
38
+ 'io' => 'IO',
39
+ 'http' => 'HTTP'
40
+ )
41
+ loader.ignore('test/**/*')
42
+ loader.ignore('bin/**/*')
43
+ loader.setup
44
+
45
+ module Dialed
46
+ class Error < StandardError; end
47
+
48
+ Client = HTTP::Client
49
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dialed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - " David Gillis"
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-02-28 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: addressable
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: async-http
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: bundler
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: zeitwerk
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ description: Supports HTTP/2, HTTP/1.X, HTTP proxying, connection pooling, concurrent
83
+ requests, and lots more
84
+ email:
85
+ - david@flipmine.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - lib/dialed.rb
91
+ - lib/dialed/http/client.rb
92
+ - lib/dialed/http/connection.rb
93
+ - lib/dialed/http/connection_builder.rb
94
+ - lib/dialed/http/dialer.rb
95
+ - lib/dialed/http/direct_connection.rb
96
+ - lib/dialed/http/explicit_client.rb
97
+ - lib/dialed/http/nil_connection.rb
98
+ - lib/dialed/http/operator.rb
99
+ - lib/dialed/http/proxy_uri.rb
100
+ - lib/dialed/http/request.rb
101
+ - lib/dialed/http/response.rb
102
+ - lib/dialed/http/response/body.rb
103
+ - lib/dialed/http/response/every_body.rb
104
+ - lib/dialed/http/response/json_body.rb
105
+ - lib/dialed/http/tunneled_connection.rb
106
+ - lib/dialed/refinements/presence.rb
107
+ - lib/dialed/version.rb
108
+ homepage: https://github.com/gillisd/dialed
109
+ licenses:
110
+ - MIT
111
+ metadata:
112
+ homepage_uri: https://github.com/gillisd/dialed
113
+ source_code_uri: https://github.com/gillisd/dialed
114
+ rubygems_mfa_required: 'true'
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 2.7.5
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 3.6.5
130
+ specification_version: 4
131
+ summary: A modern, ergonomic HTTP client built on top of async-http
132
+ test_files: []