async-aws 0.2.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 015565e72854159af66339152c90b413caa5fc32b419f3ee14b38d19c345a3ae
4
- data.tar.gz: 26f6079d3e6d1de58bd670ae306af9fa4bbfa7f8523253d984bbab6d91c7db6d
3
+ metadata.gz: d4b4634f5e07b319d4e20f927f524e59fea4236693fc38a57d914cba8e616fc8
4
+ data.tar.gz: 57868e373bbe1137a4f5a0fd38f6727f72eb895ce1a732910e3d5debec1e18b0
5
5
  SHA512:
6
- metadata.gz: 6b5bac239f67be456b553f298b1d7f35ec1701e3a4bc511110bbb71a9af0534b846f0bcdf8f66aefa6361263cc9bdb009d5eb4f9ef8faf88c75b5d7e6f03ad77
7
- data.tar.gz: 47cedb33f2cb7d5ea04759b9601e5fd95c76c697f19d96f08b9c0dc8db82773a66cc95540255b91e5c0a96d1859f43a4418ccba143cc406368ee26ea259a49f5
6
+ metadata.gz: 9acbd7be0847428db25bdc766c184d2c10eb9bd5c6facb72b266389bdcc7d276bf07e99da04cfd959d6522ce1aecf247707061d3b8b1b8f537aacd38b093f01e
7
+ data.tar.gz: 72f1485b45e5d2b8cd8be02f5ad96dc7161fd8b94e0e4989a95fcd8786e7fc63dd460533ebb1cc88dc2923ca52320dac6ae5030c643b28cb845b3aababecd5dd
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- async-aws (0.1.0)
4
+ async-aws (0.3.0)
5
5
  async-http (~> 0.48)
6
6
 
7
7
  GEM
@@ -11,14 +11,17 @@ GEM
11
11
  console (~> 1.0)
12
12
  nio4r (~> 2.3)
13
13
  timers (~> 4.1)
14
- async-http (0.48.2)
15
- async (~> 1.19)
16
- async-io (~> 1.25)
17
- protocol-http (~> 0.12.0)
18
- protocol-http1 (~> 0.9.0)
19
- protocol-http2 (~> 0.9.0)
20
- async-io (1.27.0)
14
+ async-http (0.50.0)
15
+ async (~> 1.23)
16
+ async-io (~> 1.27.0)
17
+ async-pool (~> 0.2)
18
+ protocol-http (~> 0.13.0)
19
+ protocol-http1 (~> 0.10.0)
20
+ protocol-http2 (~> 0.10.0)
21
+ async-io (1.27.1)
21
22
  async (~> 1.14)
23
+ async-pool (0.2.0)
24
+ async (~> 1.8)
22
25
  aws-eventstream (1.0.3)
23
26
  aws-partitions (1.223.0)
24
27
  aws-sdk-core (3.68.1)
@@ -43,10 +46,10 @@ GEM
43
46
  jmespath (1.4.0)
44
47
  nio4r (2.5.2)
45
48
  protocol-hpack (1.4.1)
46
- protocol-http (0.12.3)
47
- protocol-http1 (0.9.0)
48
- protocol-http (~> 0.5)
49
- protocol-http2 (0.9.7)
49
+ protocol-http (0.13.0)
50
+ protocol-http1 (0.10.0)
51
+ protocol-http (~> 0.13)
52
+ protocol-http2 (0.10.4)
50
53
  protocol-hpack (~> 1.4)
51
54
  protocol-http (~> 0.2)
52
55
  rake (10.5.0)
@@ -78,4 +81,4 @@ DEPENDENCIES
78
81
  rspec (~> 3.0)
79
82
 
80
83
  BUNDLED WITH
81
- 2.0.2
84
+ 2.1.2
@@ -1,6 +1,6 @@
1
1
  require 'async/http'
2
2
  require 'async/http/internet'
3
- require 'async/aws/connection_pool'
3
+ require 'async/aws/http_client'
4
4
  require 'async/aws/http_handler'
5
5
  require 'async/aws/http_plugin'
6
6
 
@@ -8,12 +8,24 @@ module Async
8
8
  module Aws
9
9
  module_function
10
10
 
11
- def connection_pool=(arg)
12
- @connection_pool = arg
11
+ def keep_alive_timeout=(arg)
12
+ @keep_alive_timeout = arg.to_i
13
13
  end
14
14
 
15
- def connection_pool
16
- @connection_pool ||= ConnectionPool.new
15
+ def keep_alive_timeout
16
+ @keep_alive_timeout || 2
17
+ end
18
+
19
+ def connection_pool_size=(arg)
20
+ @connection_pool_size = arg.to_i
21
+ end
22
+
23
+ def connection_pool_size
24
+ @connection_pool_size || 1
25
+ end
26
+
27
+ def configure(&block)
28
+ instance_exec(&block)
17
29
  end
18
30
  end
19
31
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'async/io/endpoint'
4
+ require 'async/io/stream'
5
+ require 'protocol/http/body/streamable'
6
+ require 'protocol/http/methods'
7
+
8
+ module Async
9
+ module Aws
10
+ class HttpClient < ::Protocol::HTTP::Methods
11
+ attr_accessor :keep_alive_timeout
12
+ attr_reader :scheme
13
+
14
+ ConnectionSpec = Struct.new(:connection, :used_at)
15
+
16
+ # Provides a robust interface to a server.
17
+ # * If there are no connections, it will create one.
18
+ # * If there are already connections, it will reuse it.
19
+ # * If a request fails, it will retry it up to N times if it was idempotent.
20
+ # The client object will never become unusable. It internally manages persistent connections (or non-persistent connections if that's required).
21
+ # @param endpoint [Endpoint] the endpoint to connnect to.
22
+ # @param protocol [Protocol::HTTP1 | Protocol::HTTP2 | Protocol::HTTPS] the protocol to use.
23
+ # @param scheme [String] The default scheme to set to requests.
24
+ # @param authority [String] The default authority to set to requests.
25
+ def initialize(endpoint, pool_size: 2, keep_alive_timeout: 5)
26
+ @endpoint = endpoint
27
+ @protocol = endpoint.protocol
28
+ @scheme = endpoint.scheme
29
+ @authority = endpoint.authority
30
+ @keep_alive_timeout = keep_alive_timeout
31
+ @clock_gettime_constant = \
32
+ if defined?(Process::CLOCK_MONOTONIC)
33
+ Process::CLOCK_MONOTONIC
34
+ else
35
+ Process::CLOCK_REALTIME
36
+ end
37
+ @pool = make_pool(pool_size)
38
+ end
39
+
40
+ def close
41
+ until @pool.empty?
42
+ connection_spec = @pool.dequeue
43
+ connection_spec.connection.close
44
+ end
45
+ end
46
+
47
+ def call(request)
48
+ request.scheme ||= self.scheme
49
+ request.authority ||= self.authority
50
+
51
+ attempt = 0
52
+
53
+ # We may retry the request if it is possible to do so. https://tools.ietf.org/html/draft-nottingham-httpbis-retry-01 is a good guide for how retrying requests should work.
54
+ begin
55
+ attempt += 1
56
+
57
+ # As we cache pool, it's possible these pool go bad (e.g. closed by remote host). In this case, we need to try again. It's up to the caller to impose a timeout on this. If this is the last attempt, we force a new connection.
58
+ connection_spec = @pool.dequeue
59
+ if connection_spec.used_at + keep_alive_timeout <= current_time
60
+ connection_spec.connection.close
61
+ connection_spec = create_connection_spec
62
+ end
63
+
64
+ # send request
65
+ response = request.call(connection_spec.connection)
66
+
67
+ # The connection won't be released until the body is completely read/released.
68
+ ::Protocol::HTTP::Body::Streamable.wrap(response) do
69
+ connection_spec.used_at = current_time
70
+ @pool << connection_spec
71
+ end
72
+
73
+ return response
74
+ rescue => e
75
+ @pool << connection_spec if connection_spec
76
+ raise e
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def current_time
83
+ Process.clock_gettime(@clock_gettime_constant)
84
+ end
85
+
86
+ def create_connection_spec
87
+ ConnectionSpec.new(
88
+ @protocol.client(@endpoint.connect),
89
+ current_time
90
+ )
91
+ end
92
+
93
+ def make_pool(size)
94
+ Async::Queue.new.tap do |queue|
95
+ size.times do
96
+ queue << create_connection_spec
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,17 +1,53 @@
1
1
  module Async
2
2
  module Aws
3
3
  class HttpHandler < ::Seahorse::Client::Handler
4
+ def self.clients
5
+ @clients ||= {}
6
+ end
7
+
8
+ def self.with_configuration(**kwargs)
9
+ Class.new(self).tap do |klass|
10
+ klass.connection_pool_size = kwargs.fetch(
11
+ :connection_pool_size, Async::Aws.connection_pool_size
12
+ )
13
+ klass.keep_alive_timeout = kwargs.fetch(
14
+ :keep_alive_timeout, Async::Aws.keep_alive_timeout
15
+ )
16
+ end
17
+ end
18
+
19
+ def self.connection_pool_size
20
+ @connection_pool_size ||= Async::Aws.connection_pool_size
21
+ end
22
+
23
+ def self.connection_pool_size=(value)
24
+ @connection_pool_size = value.to_i
25
+ end
26
+
27
+ def self.keep_alive_timeout
28
+ @keep_alive_timeout ||= Async::Aws.keep_alive_timeout
29
+ end
30
+
31
+ def self.keep_alive_timeout=(value)
32
+ @keep_alive_timeout = value.to_i
33
+ end
34
+
4
35
  def call(context)
5
36
  req = context.http_request
6
37
  resp = context.http_response
38
+ endpoint = Async::HTTP::Endpoint.parse(req.endpoint.to_s)
7
39
 
8
40
  begin
41
+ client = client_for(endpoint)
9
42
  headers_arr = req.headers.reject do |k, v|
10
43
  k == 'host' || k == 'content-length'
11
44
  end
12
- response = Async::Aws.connection_pool.call(
13
- req.http_method, req.endpoint.to_s, headers_arr, req.body
45
+ buffered_body = Async::HTTP::Body::Buffered.wrap(req.body)
46
+ request = ::Protocol::HTTP::Request.new(
47
+ client.scheme, endpoint.authority, req.http_method, endpoint.path,
48
+ nil, headers_arr, buffered_body
14
49
  )
50
+ response = client.call(request)
15
51
  body = response.read
16
52
  resp.signal_headers(response.status.to_i, response.headers.to_h)
17
53
  resp.signal_data(body)
@@ -23,6 +59,14 @@ module Async
23
59
 
24
60
  Seahorse::Client::Response.new(context: context)
25
61
  end
62
+
63
+ def client_for(endpoint)
64
+ self.class.clients[endpoint.hostname] ||= ::Async::Aws::HttpClient.new(
65
+ endpoint,
66
+ pool_size: self.class.connection_pool_size,
67
+ keep_alive_timeout: self.class.keep_alive_timeout
68
+ )
69
+ end
26
70
  end
27
71
  end
28
72
  end
@@ -1,5 +1,5 @@
1
1
  module Async
2
2
  module Aws
3
- VERSION = '0.2.0'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-aws
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julien D.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-11 00:00:00.000000000 Z
11
+ date: 2020-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-http
@@ -130,7 +130,7 @@ files:
130
130
  - lib/async-aws.rb
131
131
  - lib/async/aws.rb
132
132
  - lib/async/aws/all.rb
133
- - lib/async/aws/connection_pool.rb
133
+ - lib/async/aws/http_client.rb
134
134
  - lib/async/aws/http_handler.rb
135
135
  - lib/async/aws/http_plugin.rb
136
136
  - lib/async/aws/version.rb
@@ -157,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
157
  - !ruby/object:Gem::Version
158
158
  version: '0'
159
159
  requirements: []
160
- rubygems_version: 3.0.3
160
+ rubygems_version: 3.1.2
161
161
  signing_key:
162
162
  specification_version: 4
163
163
  summary: Async AWS SDK adapter for `socketry/async` framework
@@ -1,41 +0,0 @@
1
- module Async
2
- module Aws
3
- class ConnectionPool
4
- def initialize(connection_limit: nil)
5
- @clients = {}
6
- @connection_limit = connection_limit
7
- end
8
-
9
- def call(method, url, headers = [], body = nil)
10
- endpoint = ::Async::HTTP::Endpoint.parse(url)
11
- client = client_for(endpoint)
12
-
13
- request_body = \
14
- case body
15
- when ::Aws::Query::ParamList::IoWrapper
16
- ::Async::HTTP::Body::Buffered.wrap(body.instance_variable_get(:@io))
17
- else
18
- ::Async::HTTP::Body::Buffered.wrap(body)
19
- end
20
- request = ::Protocol::HTTP::Request.new(
21
- endpoint.scheme, endpoint.authority, method, endpoint.path, nil, headers,
22
- request_body
23
- )
24
-
25
- client.call(request)
26
- end
27
-
28
- def client_for(endpoint)
29
- @clients[endpoint] ||= ::Async::HTTP::Client.new(
30
- endpoint, endpoint.protocol, endpoint.scheme, endpoint.authority,
31
- retries: 3, connection_limit: @connection_limit
32
- )
33
- end
34
-
35
- def close
36
- @clients.each_value(&:close)
37
- @clients.clear
38
- end
39
- end
40
- end
41
- end