async-http-faraday 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8efd253e7a697bce2dbe8aafcab6cc667584242e923ee060213ee88ba40a2ce
4
- data.tar.gz: 34c1d934c5e153091a04e5208144026d5da10e6dd429c67a0770a3f25b71ecdc
3
+ metadata.gz: a24c51b0aca4a4243f088331a3731da8792287fe94f30e1b2d6f398dd521a238
4
+ data.tar.gz: '0687f09a3bc87ab6989b4cabc108aff16e21c606755c04fd77112afc40d4d124'
5
5
  SHA512:
6
- metadata.gz: 7d921cee566700dc9c82bbf9f993f20c8f912523ed75b78d7f644c0e962fce63203f38648867ebc74b600713d1240db7909d69f0199ac56e3dcef90942ac855e
7
- data.tar.gz: 8a7746559ec23c868445ae1d18201d6ac00c38ef1c950d0401d14cf62c4852c4861ad852d5bc9fe7044840b7db659ff63ef7088354fded353014f1641d04bdad
6
+ metadata.gz: ed876ca460be8b0e402240081ab6dcc9318af3e1dce3e34787c40e3bee45fff6e472bb72f30babc666d8ad7660f688d066903f09a253dfc70081fcc2d76e95ca
7
+ data.tar.gz: a1dc04a882e162e15c9161cddd48c298e027ed83fb2d5e09a0438f925277e7ad139e0f4577c13ca47f2f0164bde3a9e830cb1062a6eecbc84dbcbc52350cf785
checksums.yaml.gz.sig CHANGED
Binary file
@@ -7,6 +7,7 @@
7
7
  # Copyright, 2019-2020, by Igor Sidorov.
8
8
  # Copyright, 2023, by Genki Takiuchi.
9
9
  # Copyright, 2023, by Flavio Fernandes.
10
+ # Copyright, 2024, by Jacob Frautschi.
10
11
 
11
12
  require 'faraday'
12
13
  require 'faraday/adapter'
@@ -15,27 +16,40 @@ require 'kernel/sync'
15
16
  require 'async/http/client'
16
17
  require 'async/http/proxy'
17
18
 
19
+ require_relative 'clients'
20
+
18
21
  module Async
19
22
  module HTTP
20
23
  module Faraday
24
+ # This is a simple wrapper around Faraday's body that allows it to be read in chunks.
21
25
  class BodyReadWrapper < ::Protocol::HTTP::Body::Readable
26
+ # Create a new wrapper around the given body.
27
+ #
28
+ # The body must respond to `#read` and `#close` and is often an instance of `IO` or `Faraday::Multipart::CompositeReadIO`.
29
+ #
30
+ # @parameter body [Interface(:read)] The input body to wrap.
31
+ # @parameter block_size [Integer] The size of the blocks to read from the body.
22
32
  def initialize(body, block_size: 4096)
23
33
  @body = body
24
34
  @block_size = block_size
25
35
  end
26
36
 
37
+ # Close the body if possible.
27
38
  def close(error = nil)
28
39
  @body.close if @body.respond_to?(:close)
29
40
  ensure
30
41
  super
31
42
  end
32
43
 
44
+ # Read from the body in chunks.
33
45
  def read
34
46
  @body.read(@block_size)
35
47
  end
36
48
  end
37
49
 
50
+ # An adapter that allows Faraday to use Async::HTTP as the underlying HTTP client.
38
51
  class Adapter < ::Faraday::Adapter
52
+ # The exceptions that are considered connection errors and result in a `Faraday::ConnectionFailed` exception.
39
53
  CONNECTION_EXCEPTIONS = [
40
54
  Errno::EADDRNOTAVAIL,
41
55
  Errno::ECONNABORTED,
@@ -49,76 +63,47 @@ module Async
49
63
  SocketError
50
64
  ].freeze
51
65
 
52
- def initialize(*arguments, timeout: nil, **options, &block)
53
- super(*arguments, **options)
54
-
55
- @timeout = timeout
56
-
57
- @clients = {}
58
-
59
- @options = options
60
- end
61
-
62
- def make_client(endpoint)
63
- Client.new(endpoint, **@connection_options)
64
- end
65
-
66
- def host_key(endpoint)
67
- url = endpoint.url.dup
68
-
69
- url.path = ""
70
- url.fragment = nil
71
- url.query = nil
72
-
73
- return url
74
- end
75
-
76
- def client_for(endpoint)
77
- key = host_key(endpoint)
66
+ # Create a Farady compatible adapter.
67
+ #
68
+ # @parameter timeout [Integer] The timeout for requests.
69
+ # @parameter options [Hash] Additional options to pass to the underlying Async::HTTP::Client.
70
+ def initialize(...)
71
+ super
78
72
 
79
- @clients.fetch(key) do
80
- @clients[key] = make_client(endpoint)
81
- end
82
- end
83
-
84
- def proxy_client_for(proxy_endpoint, endpoint)
85
- key = [host_key(proxy_endpoint), host_key(endpoint)]
73
+ @timeout = @connection_options.delete(:timeout)
86
74
 
87
- @clients.fetch(key) do
88
- client = client_for(proxy_endpoint)
89
- @clients[key] = client.proxied_client(endpoint)
75
+ if clients = @connection_options.delete(:clients)
76
+ @clients = clients.call(**@connection_options, &@config_block)
77
+ else
78
+ @clients = PersistentClients.new(**@connection_options, &@config_block)
90
79
  end
91
80
  end
92
81
 
82
+ # Close all clients.
93
83
  def close
94
84
  # The order of operations here is to avoid a race condition between iterating over clients (#close may yield) and creating new clients.
95
- clients = @clients.values
96
-
97
- @clients.clear
98
-
99
- clients.each(&:close)
85
+ @clients.close
100
86
  end
101
87
 
88
+ # Make a request using the adapter.
89
+ #
90
+ # @parameter env [Faraday::Env] The environment to make the request in.
91
+ # @raises [Faraday::TimeoutError] If the request times out.
92
+ # @raises [Faraday::SSLError] If there is an SSL error.
93
+ # @raises [Faraday::ConnectionFailed] If there is a connection error.
102
94
  def call(env)
103
95
  super
104
96
 
105
- # for compatibility with the default adapter
97
+ # For compatibility with the default adapter:
106
98
  env.url.path = '/' if env.url.path.empty?
107
99
 
108
- Sync do
109
- endpoint = Endpoint.new(env.url)
110
-
111
- if proxy = env.request.proxy
112
- proxy_endpoint = Endpoint.new(proxy.uri)
113
- client = self.proxy_client_for(proxy_endpoint, endpoint)
114
- else
115
- client = self.client_for(endpoint)
116
- end
117
-
100
+ with_client(env) do |endpoint, client|
118
101
  if body = env.body
119
- # We need to wrap the body in a Readable object so that it can be read in chunks:
102
+ # We need to ensure the body is wrapped in a Readable object so that it can be read in chunks:
120
103
  # Faraday's body only responds to `#read`.
121
- if body.respond_to?(:read)
104
+ if body.is_a?(::Protocol::HTTP::Body::Readable)
105
+ # Good to go
106
+ elsif body.respond_to?(:read)
122
107
  body = BodyReadWrapper.new(body)
123
108
  else
124
109
  body = ::Protocol::HTTP::Body::Buffered.wrap(body)
@@ -151,6 +136,24 @@ module Async
151
136
 
152
137
  private
153
138
 
139
+ def with_client(env)
140
+ Sync do
141
+ endpoint = Endpoint.new(env.url)
142
+
143
+ if proxy = env.request.proxy
144
+ proxy_endpoint = Endpoint.new(proxy.uri)
145
+
146
+ @clients.with_proxied_client(proxy_endpoint, endpoint) do |client|
147
+ yield endpoint, client
148
+ end
149
+ else
150
+ @clients.with_client(endpoint) do |client|
151
+ yield endpoint, client
152
+ end
153
+ end
154
+ end
155
+ end
156
+
154
157
  def with_timeout(task: Async::Task.current)
155
158
  if @timeout
156
159
  task.with_timeout(@timeout, ::Faraday::TimeoutError) do
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
+ # Copyright, 2018, by Andreas Garnaes.
6
+ # Copyright, 2019, by Denis Talakevich.
7
+ # Copyright, 2019-2020, by Igor Sidorov.
8
+ # Copyright, 2023, by Genki Takiuchi.
9
+ # Copyright, 2023, by Flavio Fernandes.
10
+ # Copyright, 2024, by Jacob Frautschi.
11
+
12
+ require 'faraday'
13
+ require 'faraday/adapter'
14
+ require 'kernel/sync'
15
+
16
+ require 'async/http/client'
17
+ require 'async/http/proxy'
18
+
19
+ module Async
20
+ module HTTP
21
+ module Faraday
22
+ # An interface for creating and managing HTTP clients.
23
+ class Clients
24
+ # Create a new instance of the class.
25
+ def self.call(...)
26
+ new(...)
27
+ end
28
+
29
+ # Create a new interface for managing HTTP clients.
30
+ #
31
+ # @parameter options [Hash] The options to create the clients with.
32
+ # @parameter block [Proc] An optional block to call with the client before it is used.
33
+ def initialize(**options, &block)
34
+ @options = options
35
+ @block = block
36
+ end
37
+
38
+ # Close all clients.
39
+ def close
40
+ end
41
+
42
+ # Make a new client for the given endpoint.
43
+ #
44
+ # @parameter endpoint [IO::Endpoint::Generic] The endpoint to create the client for.
45
+ def make_client(endpoint)
46
+ client = Client.new(endpoint, **@options)
47
+ @block&.call(client)
48
+ return client
49
+ end
50
+
51
+ # Get a client for the given endpoint.
52
+ #
53
+ # @parameter endpoint [IO::Endpoint::Generic] The endpoint to get the client for.
54
+ # @yields {|client| ...} A client for the given endpoint.
55
+ def with_client(endpoint)
56
+ client = make_client(endpoint)
57
+
58
+ yield client
59
+ ensure
60
+ client&.close
61
+ end
62
+
63
+ # Get a client for the given proxy endpoint and endpoint.
64
+ #
65
+ # @parameter proxy_endpoint [IO::Endpoint::Generic] The proxy endpoint to use.
66
+ # @parameter endpoint [IO::Endpoint::Generic] The endpoint to get the client for.
67
+ # @yields {|client| ...} A client for the given endpoint.
68
+ def with_proxied_client(proxy_endpoint, endpoint)
69
+ client = client_for(proxy_endpoint)
70
+ proxied_client = client.proxied_client(endpoint)
71
+
72
+ yield proxied_client
73
+ ensure
74
+ proxied_client&.close
75
+ client&.close
76
+ end
77
+ end
78
+
79
+ # An interface for creating and managing persistent HTTP clients.
80
+ class PersistentClients < Clients
81
+ # Create a new instance of the class.
82
+ def initialize(...)
83
+ super
84
+
85
+ @clients = {}
86
+ end
87
+
88
+ # Close all clients.
89
+ def close
90
+ super
91
+
92
+ clients = @clients.values
93
+ @clients.clear
94
+
95
+ clients.each(&:close)
96
+ end
97
+
98
+ # Get a client for the given endpoint. If a client already exists for the host, it will be reused.
99
+ #
100
+ # @yields {|client| ...} A client for the given endpoint.
101
+ def with_client(endpoint)
102
+ yield make_client(endpoint)
103
+ end
104
+
105
+ # Get a client for the given proxy endpoint and endpoint. If a client already exists for the host, it will be reused.
106
+ #
107
+ # @parameter proxy_endpoint [IO::Endpoint::Generic] The proxy endpoint to use.
108
+ # @parameter endpoint [IO::Endpoint::Generic] The endpoint to get the client for.
109
+ def with_proxied_client(proxy_endpoint, endpoint)
110
+ key = [host_key(proxy_endpoint), host_key(endpoint)]
111
+
112
+ proxied_client = fetch(key) do
113
+ client_for(proxy_endpoint).proxied_client(endpoint)
114
+ end
115
+
116
+ yield proxied_client
117
+ end
118
+
119
+ private
120
+
121
+ def fetch(key)
122
+ @clients.fetch(key) do
123
+ @clients[key] = yield
124
+ end
125
+ end
126
+
127
+ def host_key(endpoint)
128
+ url = endpoint.url.dup
129
+
130
+ url.path = ""
131
+ url.fragment = nil
132
+ url.query = nil
133
+
134
+ return url
135
+ end
136
+
137
+ def client_for(endpoint)
138
+ key = host_key(endpoint)
139
+
140
+ fetch(key) do
141
+ make_client
142
+ end
143
+ end
144
+ end
145
+
146
+ # An interface for creating and managing per-thread persistent HTTP clients.
147
+ class PerThreadPersistentClients
148
+ # Create a new instance of the class.
149
+ #
150
+ # @parameter options [Hash] The options to create the clients with.
151
+ # @parameter block [Proc] An optional block to call with the client before it is used.
152
+ def initialize(**options, &block)
153
+ @options = options
154
+ @block = block
155
+
156
+ @key = :"#{self.class}_#{object_id}"
157
+ end
158
+
159
+ # Get a client for the given endpoint. If a client already exists for the host, it will be reused.
160
+ #
161
+ # The client instance will be will be cached per-thread.
162
+ #
163
+ # @yields {|client| ...} A client for the given endpoint.
164
+ def with_client(endpoint, &block)
165
+ clients.with_client(endpoint, &block)
166
+ end
167
+
168
+ # Get a client for the given proxy endpoint and endpoint. If a client already exists for the host, it will be reused.
169
+ #
170
+ # The client instance will be will be cached per-thread.
171
+ #
172
+ # @parameter proxy_endpoint [IO::Endpoint::Generic] The proxy endpoint to use.
173
+ # @parameter endpoint [IO::Endpoint::Generic] The endpoint to get the client for.
174
+ def with_proxied_client(proxy_endpoint, endpoint, &block)
175
+ clients.with_proxied_client(proxy_endpoint, endpoint, &block)
176
+ end
177
+
178
+ # Close all clients.
179
+ #
180
+ # This will close all clients associated with all threads.
181
+ def close
182
+ Thread.list.each do |thread|
183
+ if clients = thread[@key]
184
+ clients.close
185
+
186
+ thread[@key] = nil
187
+ end
188
+ end
189
+ end
190
+
191
+ private
192
+
193
+ def make_clients
194
+ PersistentClients.new(**@options, &@block)
195
+ end
196
+
197
+ def clients
198
+ thread = Thread.current
199
+
200
+ return thread.thread_variable_get(@key) || thread.thread_variable_set(@key, make_clients)
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -5,4 +5,5 @@
5
5
 
6
6
  require_relative 'adapter'
7
7
 
8
+ # Set the default adapter to use Async::HTTP.
8
9
  ::Faraday.default_adapter = :async_http
@@ -6,7 +6,7 @@
6
6
  module Async
7
7
  module HTTP
8
8
  module Faraday
9
- VERSION = "0.14.0"
9
+ VERSION = "0.15.0"
10
10
  end
11
11
  end
12
12
  end
@@ -7,3 +7,13 @@ require_relative "faraday/version"
7
7
  require_relative "faraday/adapter"
8
8
 
9
9
  Faraday::Adapter.register_middleware :async_http => Async::HTTP::Faraday::Adapter
10
+
11
+ # @namespace
12
+ module Async
13
+ # @namespace
14
+ module HTTP
15
+ # @namespace
16
+ module Faraday
17
+ end
18
+ end
19
+ end
data/license.md CHANGED
@@ -8,6 +8,7 @@ Copyright, 2020-2021, by Olle Jonsson.
8
8
  Copyright, 2020, by Benoit Daloze.
9
9
  Copyright, 2023, by Genki Takiuchi.
10
10
  Copyright, 2023, by Flavio Fernandes.
11
+ Copyright, 2024, by Jacob Frautschi.
11
12
 
12
13
  Permission is hereby granted, free of charge, to any person obtaining a copy
13
14
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -1,56 +1,17 @@
1
1
  # Async::HTTP::Faraday
2
2
 
3
- Provides an adaptor for [Faraday](https://github.com/lostisland/faraday) to perform async HTTP requests. If you are designing a new library, you should probably just use `Async::HTTP::Client` directly.
3
+ Provides an adaptor for [Faraday](https://github.com/lostisland/faraday) to perform async HTTP requests. If you are designing a new library, you should probably just use `Async::HTTP::Client` directly. However, for existing projects and libraries that use Faraday as an abstract interface, this can be a drop-in replacement to improve concurrency. It should be noted that the default `Net::HTTP` adapter works perfectly okay with Async, however it does not use persistent connections by default.
4
4
 
5
- [![Development Status](https://github.com/socketry/async-http-faraday/workflows/Test/badge.svg)](https://github.com/socketry/async-http-faraday/actions?workflow=Test)
6
-
7
- ## Installation
8
-
9
- Add this line to your application's Gemfile:
10
-
11
- ``` ruby
12
- gem 'async-http-faraday'
13
- ```
5
+ - Persistent connections by default.
6
+ - Supports HTTP/1 and HTTP/2 (and HTTP/3 in the future).
14
7
 
15
- And then execute:
16
-
17
- $ bundle
18
-
19
- Or install it yourself as:
20
-
21
- $ gem install async-http-faraday
8
+ [![Development Status](https://github.com/socketry/async-http-faraday/workflows/Test/badge.svg)](https://github.com/socketry/async-http-faraday/actions?workflow=Test)
22
9
 
23
10
  ## Usage
24
11
 
25
- Here is how you set faraday to use `Async::HTTP`:
26
-
27
- ``` ruby
28
- require 'async/http/faraday'
29
-
30
- # Make it the global default:
31
- Faraday.default_adapter = :async_http
32
-
33
- # Per connection:
34
- connection = Faraday.new(...) do |builder|
35
- builder.adapter :async_http
36
- end
37
- ```
38
-
39
- Here is how you make a request:
40
-
41
- ``` ruby
42
- Async do
43
- response = connection.get("/index")
44
- end
45
- ```
46
-
47
- ### Default
48
-
49
- To make this the default adaptor:
12
+ Please see the [project documentation](https://socketry.github.io/async-http/) for more details.
50
13
 
51
- ``` ruby
52
- require 'async/http/faraday/default'
53
- ```
14
+ - [Getting Started](https://socketry.github.io/async-http/guides/getting-started/index) - This guide explains how to use use `Async::HTTP::Faraday` as a drop-in replacement for improved concurrency.
54
15
 
55
16
  ## Contributing
56
17
 
@@ -64,8 +25,8 @@ We welcome contributions to this project.
64
25
 
65
26
  ### Developer Certificate of Origin
66
27
 
67
- This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted.
28
+ In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
68
29
 
69
- ### Contributor Covenant
30
+ ### Community Guidelines
70
31
 
71
- This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.
32
+ This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-http-faraday
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -12,6 +12,7 @@ authors:
12
12
  - Benoit Daloze
13
13
  - Denis Talakevich
14
14
  - Flavio Fernandes
15
+ - Jacob Frautschi
15
16
  autorequire:
16
17
  bindir: bin
17
18
  cert_chain:
@@ -44,7 +45,7 @@ cert_chain:
44
45
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
45
46
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
46
47
  -----END CERTIFICATE-----
47
- date: 2024-04-23 00:00:00.000000000 Z
48
+ date: 2024-07-31 00:00:00.000000000 Z
48
49
  dependencies:
49
50
  - !ruby/object:Gem::Dependency
50
51
  name: async-http
@@ -83,6 +84,7 @@ files:
83
84
  - examples/topics.rb
84
85
  - lib/async/http/faraday.rb
85
86
  - lib/async/http/faraday/adapter.rb
87
+ - lib/async/http/faraday/clients.rb
86
88
  - lib/async/http/faraday/default.rb
87
89
  - lib/async/http/faraday/version.rb
88
90
  - license.md
@@ -108,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
110
  - !ruby/object:Gem::Version
109
111
  version: '0'
110
112
  requirements: []
111
- rubygems_version: 3.5.3
113
+ rubygems_version: 3.5.11
112
114
  signing_key:
113
115
  specification_version: 4
114
116
  summary: Provides an adaptor between async-http and faraday.
metadata.gz.sig CHANGED
Binary file