ezclient 1.7.2 → 1.7.3

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: f95857956414f9377de79dd8b0b29bc54de9dcb57ef0cdcff1e2964f14030d3f
4
- data.tar.gz: 0e37a6cda106514431b61640582e89b97434c59bd8e281549a1e1de528e18ab3
3
+ metadata.gz: b9d924fb42fdf0d2417695af6b836c5a5901a93ebf49e427810b53e9f39eb895
4
+ data.tar.gz: 847bf2ad2e91ec4671d24c23f51cff227540bbdc20701bd7d45ed352ade9777f
5
5
  SHA512:
6
- metadata.gz: d3541368eac52018a2ef482e41d13fc785466da7be953d4a6a9f44ea86ce66a0498d844f8ff86cd5b6048fbb334ad508249273ad0fa8f91fadf3cca0e9430609
7
- data.tar.gz: ada38c6791768ceb5a3f01877b0236b9400392b32dc4f32d0b9a4c6299bd685f13c38499c49b3e23c63f9461c4e18e3b9829242ce5a804043d06be9b6bc19194
6
+ metadata.gz: a0166d4e24074988276d31315675ff36565bb398c49970f554b3a5b903f5ad98b73428bfeb8ac0e65dd49902d48c3099a29a60076dcba96f2882281003336969
7
+ data.tar.gz: d5d6eb11f69e26aa5d4b51d5cc52c0afa30bf2a36ae1fe0dfb9e40ae9540df33b73e735a9525173bb814cb2e790fd9547c867941b83d7f0c3990a4811cb0091a
@@ -29,3 +29,29 @@ jobs:
29
29
  - uses: coverallsapp/github-action@v2
30
30
  with:
31
31
  github-token: ${{ secrets.GITHUB_TOKEN }}
32
+
33
+ test-http-6:
34
+ runs-on: ubuntu-latest
35
+
36
+ # We want to run on external PRs, but not on our own internal PRs as they'll be run on push event
37
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'umbrellio/ezclient'
38
+
39
+ strategy:
40
+ fail-fast: false
41
+ matrix:
42
+ ruby: ["3.2", "3.3"]
43
+
44
+ name: http 6 / Ruby ${{ matrix.ruby }}
45
+
46
+ env:
47
+ BUNDLE_GEMFILE: gemfiles/http_6.gemfile
48
+
49
+ steps:
50
+ - uses: actions/checkout@v4
51
+
52
+ - uses: ruby/setup-ruby@v1
53
+ with:
54
+ ruby-version: ${{ matrix.ruby }}
55
+ bundler-cache: true
56
+
57
+ - run: bundle exec rspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ezclient (1.7.2)
4
+ ezclient (1.7.3)
5
5
  http (>= 4)
6
6
 
7
7
  GEM
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ eval_gemfile "../Gemfile"
6
+
7
+ gem "http", "~> 6.0"
@@ -0,0 +1,187 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ ezclient (1.7.3)
5
+ http (>= 4)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (8.1.3)
11
+ base64
12
+ bigdecimal
13
+ concurrent-ruby (~> 1.0, >= 1.3.1)
14
+ connection_pool (>= 2.2.5)
15
+ drb
16
+ i18n (>= 1.6, < 2)
17
+ json
18
+ logger (>= 1.4.2)
19
+ minitest (>= 5.1)
20
+ securerandom (>= 0.3)
21
+ tzinfo (~> 2.0, >= 2.0.5)
22
+ uri (>= 0.13.1)
23
+ addressable (2.9.0)
24
+ public_suffix (>= 2.0.2, < 8.0)
25
+ ast (2.4.3)
26
+ base64 (0.3.0)
27
+ bigdecimal (4.1.2)
28
+ coderay (1.1.3)
29
+ concurrent-ruby (1.3.6)
30
+ connection_pool (3.0.2)
31
+ coveralls (0.7.2)
32
+ multi_json (~> 1.3)
33
+ rest-client (= 1.6.7)
34
+ simplecov (>= 0.7)
35
+ term-ansicolor (= 1.2.2)
36
+ thor (= 0.18.1)
37
+ crack (1.0.1)
38
+ bigdecimal
39
+ rexml
40
+ diff-lcs (1.6.2)
41
+ docile (1.4.1)
42
+ domain_name (0.6.20240107)
43
+ drb (2.2.3)
44
+ hashdiff (1.2.1)
45
+ http (6.0.3)
46
+ http-cookie (~> 1.0)
47
+ llhttp (~> 0.6.1)
48
+ http-cookie (1.1.6)
49
+ domain_name (~> 0.5)
50
+ i18n (1.14.8)
51
+ concurrent-ruby (~> 1.0)
52
+ io-console (0.8.2)
53
+ json (2.19.4)
54
+ language_server-protocol (3.17.0.5)
55
+ lint_roller (1.1.0)
56
+ llhttp (0.6.1)
57
+ logger (1.7.0)
58
+ method_source (1.1.0)
59
+ mime-types (3.7.0)
60
+ logger
61
+ mime-types-data (~> 3.2025, >= 3.2025.0507)
62
+ mime-types-data (3.2026.0414)
63
+ minitest (6.0.5)
64
+ drb (~> 2.0)
65
+ prism (~> 1.5)
66
+ multi_json (1.20.1)
67
+ parallel (1.28.0)
68
+ parser (3.3.11.1)
69
+ ast (~> 2.4.1)
70
+ racc
71
+ prism (1.9.0)
72
+ pry (0.16.0)
73
+ coderay (~> 1.1)
74
+ method_source (~> 1.0)
75
+ reline (>= 0.6.0)
76
+ public_suffix (7.0.5)
77
+ racc (1.8.1)
78
+ rack (3.2.6)
79
+ rainbow (3.1.1)
80
+ rake (13.4.2)
81
+ regexp_parser (2.12.0)
82
+ reline (0.6.3)
83
+ io-console (~> 0.5)
84
+ rest-client (1.6.7)
85
+ mime-types (>= 1.16)
86
+ rexml (3.4.4)
87
+ rspec (3.13.2)
88
+ rspec-core (~> 3.13.0)
89
+ rspec-expectations (~> 3.13.0)
90
+ rspec-mocks (~> 3.13.0)
91
+ rspec-core (3.13.6)
92
+ rspec-support (~> 3.13.0)
93
+ rspec-expectations (3.13.5)
94
+ diff-lcs (>= 1.2.0, < 2.0)
95
+ rspec-support (~> 3.13.0)
96
+ rspec-mocks (3.13.8)
97
+ diff-lcs (>= 1.2.0, < 2.0)
98
+ rspec-support (~> 3.13.0)
99
+ rspec-support (3.13.7)
100
+ rubocop (1.84.2)
101
+ json (~> 2.3)
102
+ language_server-protocol (~> 3.17.0.2)
103
+ lint_roller (~> 1.1.0)
104
+ parallel (~> 1.10)
105
+ parser (>= 3.3.0.2)
106
+ rainbow (>= 2.2.2, < 4.0)
107
+ regexp_parser (>= 2.9.3, < 3.0)
108
+ rubocop-ast (>= 1.49.0, < 2.0)
109
+ ruby-progressbar (~> 1.7)
110
+ unicode-display_width (>= 2.4.0, < 4.0)
111
+ rubocop-ast (1.49.1)
112
+ parser (>= 3.3.7.2)
113
+ prism (~> 1.7)
114
+ rubocop-config-umbrellio (1.84.118)
115
+ rubocop (~> 1.84.0)
116
+ rubocop-factory_bot (~> 2.28.0)
117
+ rubocop-performance (~> 1.26.0)
118
+ rubocop-rails (~> 2.34.0)
119
+ rubocop-rake (~> 0.7.0)
120
+ rubocop-rspec (~> 3.9.0)
121
+ rubocop-sequel (~> 0.4.0)
122
+ rubocop-factory_bot (2.28.0)
123
+ lint_roller (~> 1.1)
124
+ rubocop (~> 1.72, >= 1.72.1)
125
+ rubocop-performance (1.26.1)
126
+ lint_roller (~> 1.1)
127
+ rubocop (>= 1.75.0, < 2.0)
128
+ rubocop-ast (>= 1.47.1, < 2.0)
129
+ rubocop-rails (2.34.3)
130
+ activesupport (>= 4.2.0)
131
+ lint_roller (~> 1.1)
132
+ rack (>= 1.1)
133
+ rubocop (>= 1.75.0, < 2.0)
134
+ rubocop-ast (>= 1.44.0, < 2.0)
135
+ rubocop-rake (0.7.1)
136
+ lint_roller (~> 1.1)
137
+ rubocop (>= 1.72.1)
138
+ rubocop-rspec (3.9.0)
139
+ lint_roller (~> 1.1)
140
+ rubocop (~> 1.81)
141
+ rubocop-sequel (0.4.1)
142
+ lint_roller (~> 1.1)
143
+ rubocop (>= 1.72.1, < 2)
144
+ ruby-progressbar (1.13.0)
145
+ securerandom (0.4.1)
146
+ simplecov (0.22.0)
147
+ docile (~> 1.1)
148
+ simplecov-html (~> 0.11)
149
+ simplecov_json_formatter (~> 0.1)
150
+ simplecov-html (0.13.2)
151
+ simplecov-lcov (0.9.0)
152
+ simplecov_json_formatter (0.1.4)
153
+ term-ansicolor (1.2.2)
154
+ tins (~> 0.8)
155
+ thor (0.18.1)
156
+ tins (0.13.2)
157
+ tzinfo (2.0.6)
158
+ concurrent-ruby (~> 1.0)
159
+ unicode-display_width (3.2.0)
160
+ unicode-emoji (~> 4.1)
161
+ unicode-emoji (4.2.0)
162
+ uri (1.1.1)
163
+ webmock (3.26.2)
164
+ addressable (>= 2.8.0)
165
+ crack (>= 0.3.2)
166
+ hashdiff (>= 0.4.0, < 2.0.0)
167
+
168
+ PLATFORMS
169
+ arm64-darwin-24
170
+ ruby
171
+
172
+ DEPENDENCIES
173
+ bundler
174
+ coveralls
175
+ ezclient!
176
+ http (~> 6.0)
177
+ pry
178
+ rake
179
+ rspec
180
+ rubocop-config-umbrellio
181
+ rubocop-rake
182
+ simplecov
183
+ simplecov-lcov
184
+ webmock
185
+
186
+ BUNDLED WITH
187
+ 2.7.2
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EzClient::HttprbCompatibility
4
+ KEYWORD_PARAMETER_TYPES = %i[key keyreq].freeze
5
+
6
+ module_function
7
+
8
+ def client_supports_build_request?
9
+ HTTP::Client.method_defined?(:build_request)
10
+ end
11
+
12
+ def httprb_v6_or_later?
13
+ Gem::Version.new(HTTP::VERSION) >= Gem::Version.new("6")
14
+ end
15
+
16
+ def build_request(client, verb, url, opts)
17
+ if client_supports_build_request?
18
+ client.build_request(verb, url, opts)
19
+ else
20
+ HTTP::Request::Builder.new(client.default_options.merge(opts)).build(verb, url)
21
+ end
22
+ end
23
+
24
+ def basic_auth(client, opts)
25
+ if keyword_initializer?(client.method(:basic_auth))
26
+ client.basic_auth(**opts)
27
+ else
28
+ client.basic_auth(opts)
29
+ end
30
+ end
31
+
32
+ def redirector(opts)
33
+ if keyword_initializer?(HTTP::Redirector.instance_method(:initialize))
34
+ HTTP::Redirector.new(**opts)
35
+ else
36
+ HTTP::Redirector.new(opts)
37
+ end
38
+ end
39
+
40
+ def persistent_client(origin, keep_alive_timeout)
41
+ if client_supports_build_request?
42
+ HTTP.persistent(origin, timeout: keep_alive_timeout)
43
+ else
44
+ HTTP::Client.new(persistent: origin, keep_alive_timeout: keep_alive_timeout)
45
+ end
46
+ end
47
+
48
+ def response(**attrs)
49
+ if keyword_initializer?(HTTP::Response.instance_method(:initialize))
50
+ HTTP::Response.new(**attrs)
51
+ else
52
+ HTTP::Response.new(attrs)
53
+ end
54
+ end
55
+
56
+ def keyword_initializer?(method)
57
+ method.parameters.any? { |type, _name| KEYWORD_PARAMETER_TYPES.include?(type) }
58
+ end
59
+ end
@@ -3,7 +3,7 @@
3
3
  class EzClient::PersistentClient
4
4
  extend Forwardable
5
5
 
6
- def_delegators :http_client, :build_request, :default_options, :timeout
6
+ def_delegators :http_client, :basic_auth, :build_request, :cookies, :default_options, :timeout
7
7
 
8
8
  def initialize(origin, keep_alive_timeout)
9
9
  self.origin = origin
@@ -26,6 +26,6 @@ class EzClient::PersistentClient
26
26
  attr_accessor :origin, :keep_alive_timeout, :last_request_at
27
27
 
28
28
  def http_client
29
- @http_client ||= HTTP.persistent(origin, timeout: keep_alive_timeout)
29
+ @http_client ||= EzClient::HttprbCompatibility.persistent_client(origin, keep_alive_timeout)
30
30
  end
31
31
  end
@@ -10,6 +10,51 @@ class EzClient::Request
10
10
  query
11
11
  ].freeze
12
12
 
13
+ class RedirectCookieState
14
+ def initialize(response)
15
+ self.cookie_jar = HTTP::CookieJar.new
16
+ self.expired_cookie_names = []
17
+ store(response)
18
+ end
19
+
20
+ def store(response)
21
+ response.headers.get(HTTP::Headers::SET_COOKIE).each do |set_cookie|
22
+ HTTP::Cookie.parse(set_cookie, response.request.uri).each do |cookie|
23
+ expired_cookie_names << cookie.name if cookie.expired?
24
+ cookie_jar.add(cookie)
25
+ end
26
+ end
27
+ end
28
+
29
+ def apply_to(request)
30
+ cookies = cookie_header_for(request)
31
+
32
+ if cookies.empty?
33
+ request.headers.delete(HTTP::Headers::COOKIE)
34
+ else
35
+ request.headers.set(HTTP::Headers::COOKIE, cookies)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ attr_accessor :cookie_jar, :expired_cookie_names
42
+
43
+ def cookie_header_for(request)
44
+ response_cookies = cookie_jar.cookies(request.uri)
45
+ excluded_names = expired_cookie_names | response_cookies.map(&:name)
46
+
47
+ cookie_values = request_cookie_values(request, excluded_names)
48
+ cookie_values.concat(response_cookies.map(&:cookie_value)).join("; ")
49
+ end
50
+
51
+ def request_cookie_values(request, excluded_names)
52
+ HTTP::Cookie.cookie_value_to_hash(request.headers[HTTP::Headers::COOKIE].to_s)
53
+ .except(*excluded_names)
54
+ .map { |name, value| "#{name}=#{HTTP::Cookie::Scanner.quote(value)}" }
55
+ end
56
+ end
57
+
13
58
  attr_accessor :verb, :url, :options, :elapsed_seconds
14
59
 
15
60
  def initialize(verb, url, options)
@@ -43,7 +88,8 @@ class EzClient::Request
43
88
 
44
89
  def api_auth!(*args)
45
90
  raise "ApiAuth gem is not loaded" unless defined?(ApiAuth)
46
- ApiAuth.sign!(http_request, *args)
91
+
92
+ ApiAuth.sign!(api_auth_request, *args)
47
93
  self
48
94
  end
49
95
 
@@ -73,28 +119,45 @@ class EzClient::Request
73
119
 
74
120
  attr_accessor :client
75
121
 
122
+ def api_auth_request
123
+ http_request.tap { |request| define_api_auth_header_accessors(request) }
124
+ end
125
+
126
+ def define_api_auth_header_accessors(request)
127
+ # api-auth 2.x expects HTTP::Request to expose header accessors that were removed in httprb 6.
128
+ request.define_singleton_method(:[]) { |key| headers[key] } unless request.respond_to?(:[])
129
+
130
+ return if request.respond_to?(:[]=)
131
+
132
+ request.define_singleton_method(:[]=) { |key, value| headers[key] = value }
133
+ end
134
+
76
135
  def http_request
77
- @http_request ||= begin
78
- opts = {}
79
-
80
- opts[verb == "GET" ? :params : :form] = options[:params]
81
- opts[:json] = options[:json] if options[:json]
82
- opts[:body] = options[:body] if options[:body]
83
- opts[:params] = options[:query] if options[:query]
84
- opts[:form] = options[:form] if options[:form]
85
- opts[:form] = prepare_form_params(opts[:form]) if opts[:form]
86
- opts[:headers] = prepare_headers(options[:headers])
87
-
88
- http_client.build_request(verb, url, opts)
89
- end
136
+ @http_request ||= EzClient::HttprbCompatibility.build_request(
137
+ http_client,
138
+ verb,
139
+ url,
140
+ build_request_opts,
141
+ )
142
+ end
143
+
144
+ def build_request_opts
145
+ opts = {}
146
+ opts[verb == "GET" ? :params : :form] = options[:params] if options[:params]
147
+ opts[:json] = options[:json] if options[:json]
148
+ opts[:body] = options[:body] if options[:body]
149
+ opts[:params] = options[:query] if options[:query]
150
+ opts[:form] = options[:form] if options[:form]
151
+ opts[:form] = prepare_form_params(opts[:form]) if opts[:form]
152
+ opts[:headers] = prepare_headers(options[:headers])
153
+ opts
90
154
  end
91
155
 
92
156
  def http_client
93
- # Only used to build proper HTTP::Request and HTTP::Options instances
94
157
  @http_client ||= begin
95
158
  http_client = client.dup
96
159
  http_client = set_timeout(http_client)
97
- http_client = http_client.basic_auth(basic_auth) if basic_auth
160
+ http_client = EzClient::HttprbCompatibility.basic_auth(http_client, basic_auth) if basic_auth
98
161
  http_client = http_client.cookies(options[:cookies]) if options[:cookies]
99
162
  http_client
100
163
  end
@@ -107,14 +170,41 @@ class EzClient::Request
107
170
  res = client.perform(http_request, http_options)
108
171
  return res unless follow
109
172
 
110
- HTTP::Redirector.new(follow).perform(http_request, res) do |request|
111
- client.perform(request, http_options)
112
- end
173
+ perform_redirects(res)
113
174
  end
114
175
  ensure
115
176
  self.elapsed_seconds = EzClient.get_time - perform_started_at
116
177
  end
117
178
 
179
+ def perform_redirects(response)
180
+ if EzClient::HttprbCompatibility.client_supports_build_request?
181
+ redirector(follow).perform(http_request, response) { |req| client.perform(req, http_options) }
182
+ else
183
+ perform_redirects_with_cookies(response)
184
+ end
185
+ end
186
+
187
+ def perform_redirects_with_cookies(response)
188
+ cookie_state = RedirectCookieState.new(response)
189
+ redirect_opts = follow.dup
190
+ on_redirect = redirect_opts.delete(:on_redirect)
191
+ redirect_response = response
192
+
193
+ redirector(redirect_opts).perform(http_request, response) do |req|
194
+ cookie_state.apply_to(req)
195
+ on_redirect&.call(redirect_response, req)
196
+
197
+ client.perform(req, http_options).tap do |res|
198
+ cookie_state.store(res)
199
+ redirect_response = res
200
+ end
201
+ end
202
+ end
203
+
204
+ def redirector(options)
205
+ EzClient::HttprbCompatibility.redirector(options)
206
+ end
207
+
118
208
  def with_retry(&block)
119
209
  retries = 0
120
210
 
@@ -187,7 +277,6 @@ class EzClient::Request
187
277
  def prepare_form_params(original_params)
188
278
  params = {}
189
279
 
190
- # NOTE: use Hash#transform_values after Ruby 2.3 support is dropped
191
280
  original_params.each do |key, value|
192
281
  params[key] =
193
282
  if value.is_a?(File)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EzClient
4
- VERSION = "1.7.2"
4
+ VERSION = "1.7.3"
5
5
  end
data/lib/ezclient.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "http"
4
4
 
5
5
  require_relative "ezclient/version"
6
+ require_relative "ezclient/httprb_compatibility"
6
7
  require_relative "ezclient/client"
7
8
  require_relative "ezclient/persistent_client"
8
9
  require_relative "ezclient/persistent_client_registry"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ezclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuri Smirnov
@@ -42,10 +42,13 @@ files:
42
42
  - bin/console
43
43
  - bin/rspec
44
44
  - ezclient.gemspec
45
+ - gemfiles/http_6.gemfile
46
+ - gemfiles/http_6.gemfile.lock
45
47
  - lib/ezclient.rb
46
48
  - lib/ezclient/check_options.rb
47
49
  - lib/ezclient/client.rb
48
50
  - lib/ezclient/errors.rb
51
+ - lib/ezclient/httprb_compatibility.rb
49
52
  - lib/ezclient/persistent_client.rb
50
53
  - lib/ezclient/persistent_client_registry.rb
51
54
  - lib/ezclient/request.rb
@@ -69,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
72
  - !ruby/object:Gem::Version
70
73
  version: '0'
71
74
  requirements: []
72
- rubygems_version: 3.6.7
75
+ rubygems_version: 3.6.9
73
76
  specification_version: 4
74
77
  summary: An HTTP gem wrapper for easy persistent connections and more.
75
78
  test_files: []