faraday 2.15.0 → 2.15.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 +4 -4
- data/Rakefile +11 -5
- data/lib/faraday/connection.rb +13 -6
- data/lib/faraday/encoders/nested_params_encoder.rb +1 -1
- data/lib/faraday/error.rb +29 -2
- data/lib/faraday/logging/formatter.rb +1 -1
- data/lib/faraday/options.rb +2 -1
- data/lib/faraday/rack_builder.rb +3 -0
- data/lib/faraday/response/raise_error.rb +7 -1
- data/lib/faraday/response.rb +5 -2
- data/lib/faraday/utils/headers.rb +41 -7
- data/lib/faraday/utils.rb +15 -3
- data/lib/faraday/version.rb +1 -1
- data/spec/external_adapters/faraday_specs_setup.rb +2 -0
- data/spec/support/integration/test_pki.rb +77 -0
- data/spec/support/integration/tls_server.rb +92 -0
- data/spec/support/integration_helper.rb +71 -0
- metadata +5 -29
- data/spec/faraday/adapter/test_spec.rb +0 -460
- data/spec/faraday/adapter_registry_spec.rb +0 -28
- data/spec/faraday/adapter_spec.rb +0 -55
- data/spec/faraday/connection_spec.rb +0 -897
- data/spec/faraday/error_spec.rb +0 -175
- data/spec/faraday/middleware_registry_spec.rb +0 -31
- data/spec/faraday/middleware_spec.rb +0 -213
- data/spec/faraday/options/env_spec.rb +0 -76
- data/spec/faraday/options/options_spec.rb +0 -331
- data/spec/faraday/options/proxy_options_spec.rb +0 -85
- data/spec/faraday/options/request_options_spec.rb +0 -19
- data/spec/faraday/params_encoders/flat_spec.rb +0 -42
- data/spec/faraday/params_encoders/nested_spec.rb +0 -179
- data/spec/faraday/rack_builder_spec.rb +0 -323
- data/spec/faraday/request/authorization_spec.rb +0 -118
- data/spec/faraday/request/instrumentation_spec.rb +0 -74
- data/spec/faraday/request/json_spec.rb +0 -199
- data/spec/faraday/request/url_encoded_spec.rb +0 -93
- data/spec/faraday/request_spec.rb +0 -119
- data/spec/faraday/response/json_spec.rb +0 -206
- data/spec/faraday/response/logger_spec.rb +0 -299
- data/spec/faraday/response/raise_error_spec.rb +0 -286
- data/spec/faraday/response_spec.rb +0 -84
- data/spec/faraday/utils/headers_spec.rb +0 -109
- data/spec/faraday/utils_spec.rb +0 -120
- data/spec/faraday_spec.rb +0 -69
- data/spec/spec_helper.rb +0 -142
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a700341d7d0844ede1248c479375ec0b3cb7fd51526c7c4abbaf9ce3db6c8a44
|
|
4
|
+
data.tar.gz: 76de6bace7c8995002b54ce7f19610a311418ef253c19a2fb354dee8ea986866
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5bc51dff2b4420115e15f36532fa2f9c2a31aa60d28d9acd864dcd4a50185e28fe5ac4dbeab648915d6a605e3606d06e207c083b3387a3253040bc9c108d6264
|
|
7
|
+
data.tar.gz: 82893f3f9d2152459d2a7b9b271601926e723a0723b7c3d20dc615795ce3d210473f2b6f359ec68fb4cd0f0a590ecae3121c325a257040c33701094cf56966fb
|
data/Rakefile
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'rspec/core/rake_task'
|
|
4
3
|
require 'bundler'
|
|
5
4
|
|
|
6
5
|
Bundler::GemHelper.install_tasks
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
# The publish workflow sets FARADAY_RELEASE because its bundle (release/Gemfile) doesn't include RSpec.
|
|
8
|
+
unless ENV['FARADAY_RELEASE']
|
|
9
|
+
require 'rspec/core/rake_task'
|
|
10
|
+
|
|
11
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
|
12
|
+
task.ruby_opts = %w[-W]
|
|
13
|
+
# Integration specs need service containers, see .github/workflows/integration-tests.yml
|
|
14
|
+
task.exclude_pattern = 'spec/integration/**/*_spec.rb'
|
|
15
|
+
end
|
|
11
16
|
|
|
12
|
-
task default: :spec
|
|
17
|
+
task default: :spec
|
|
18
|
+
end
|
data/lib/faraday/connection.rb
CHANGED
|
@@ -300,7 +300,10 @@ module Faraday
|
|
|
300
300
|
end
|
|
301
301
|
end
|
|
302
302
|
|
|
303
|
-
# Determine if this Faraday::Connection
|
|
303
|
+
# Determine if this Faraday::Connection is currently making parallel
|
|
304
|
+
# requests, i.e. if it is called inside an {#in_parallel} block.
|
|
305
|
+
# Use {#support_parallel?} to check if the adapter can make parallel
|
|
306
|
+
# requests at all.
|
|
304
307
|
#
|
|
305
308
|
# @return [Boolean]
|
|
306
309
|
def in_parallel?
|
|
@@ -362,7 +365,7 @@ module Faraday
|
|
|
362
365
|
#
|
|
363
366
|
# conn.get("nigiri?page=2") # accesses https://httpbingo.org/api/nigiri
|
|
364
367
|
def url_prefix=(url, encoder = nil)
|
|
365
|
-
uri = @url_prefix = Utils.URI(url)
|
|
368
|
+
uri = @url_prefix = Utils.URI(url.to_s)
|
|
366
369
|
self.path_prefix = uri.path
|
|
367
370
|
|
|
368
371
|
params.merge_query(uri.query, encoder)
|
|
@@ -445,7 +448,7 @@ module Faraday
|
|
|
445
448
|
req.options.proxy = proxy_for_request(url)
|
|
446
449
|
req.url(url) if url
|
|
447
450
|
req.headers.update(headers) if headers
|
|
448
|
-
req.body = body
|
|
451
|
+
req.body = body unless body.nil?
|
|
449
452
|
yield(req) if block_given?
|
|
450
453
|
end
|
|
451
454
|
|
|
@@ -496,8 +499,6 @@ module Faraday
|
|
|
496
499
|
|
|
497
500
|
# Creates a duplicate of this Faraday::Connection.
|
|
498
501
|
#
|
|
499
|
-
# @api private
|
|
500
|
-
#
|
|
501
502
|
# @return [Faraday::Connection]
|
|
502
503
|
def dup
|
|
503
504
|
self.class.new(build_exclusive_url,
|
|
@@ -560,7 +561,13 @@ module Faraday
|
|
|
560
561
|
end
|
|
561
562
|
end
|
|
562
563
|
|
|
563
|
-
|
|
564
|
+
# Determine if an adapter can make parallel requests.
|
|
565
|
+
#
|
|
566
|
+
# @param adapter [Class, nil] the adapter class to check. Defaults to the
|
|
567
|
+
# adapter of this Faraday::Connection.
|
|
568
|
+
#
|
|
569
|
+
# @return [Boolean]
|
|
570
|
+
def support_parallel?(adapter = @builder.adapter&.klass)
|
|
564
571
|
adapter.respond_to?(:supports_parallel?) && adapter&.supports_parallel?
|
|
565
572
|
end
|
|
566
573
|
end
|
data/lib/faraday/error.rb
CHANGED
|
@@ -70,7 +70,7 @@ module Faraday
|
|
|
70
70
|
# to `super`. See NilStatusError.
|
|
71
71
|
def exc_msg_and_response!(exc, response = nil)
|
|
72
72
|
if @response.nil? && @wrapped_exception.nil?
|
|
73
|
-
@wrapped_exception, msg, @response = exc_msg_and_response(exc, response)
|
|
73
|
+
@wrapped_exception, msg, @response = exc_msg_and_response(exc, omit_request_data(response))
|
|
74
74
|
return msg
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -85,7 +85,14 @@ module Faraday
|
|
|
85
85
|
when Hash
|
|
86
86
|
[nil, build_error_message_from_hash(exc), exc]
|
|
87
87
|
when Faraday::Env
|
|
88
|
-
|
|
88
|
+
sanitized = omit_request_data(exc)
|
|
89
|
+
message =
|
|
90
|
+
if sanitized.equal?(exc)
|
|
91
|
+
build_error_message_from_env(exc)
|
|
92
|
+
else
|
|
93
|
+
build_error_message_from_hash(sanitized)
|
|
94
|
+
end
|
|
95
|
+
[nil, message, sanitized]
|
|
89
96
|
else
|
|
90
97
|
[nil, exc.to_s, response]
|
|
91
98
|
end
|
|
@@ -93,6 +100,26 @@ module Faraday
|
|
|
93
100
|
|
|
94
101
|
private
|
|
95
102
|
|
|
103
|
+
# When the RaiseError middleware opted this request out of request data
|
|
104
|
+
# (`include_request: false`), it marks the Env. Replace env-carrying
|
|
105
|
+
# responses (the Response object raised by the Json middleware, or an Env
|
|
106
|
+
# passed directly) with a request-less hash so that errors raised by other
|
|
107
|
+
# middlewares (e.g. ParsingError) cannot expose request headers and body.
|
|
108
|
+
# The body reads the explicit `response_body` member because `Env#body`
|
|
109
|
+
# aliases to the request body while the status is nil.
|
|
110
|
+
def omit_request_data(response)
|
|
111
|
+
env =
|
|
112
|
+
case response
|
|
113
|
+
when Faraday::Response
|
|
114
|
+
response.env if response.finished?
|
|
115
|
+
when Faraday::Env
|
|
116
|
+
response
|
|
117
|
+
end
|
|
118
|
+
return response unless env.is_a?(Faraday::Env) && env[:include_request] == false
|
|
119
|
+
|
|
120
|
+
{ status: env.status, headers: env.response_headers, body: env.response_body }
|
|
121
|
+
end
|
|
122
|
+
|
|
96
123
|
def build_error_message_from_hash(hash)
|
|
97
124
|
# Be defensive with external Hash objects - they might be missing keys
|
|
98
125
|
status = hash.fetch(:status, nil)
|
|
@@ -41,7 +41,7 @@ module Faraday
|
|
|
41
41
|
def exception(exc)
|
|
42
42
|
return unless log_errors?
|
|
43
43
|
|
|
44
|
-
public_send(log_level) { "error: #{exc.full_message}" }
|
|
44
|
+
public_send(log_level) { "error: #{apply_filters(exc.full_message)}" }
|
|
45
45
|
|
|
46
46
|
log_headers('error', exc.response_headers) if exc.respond_to?(:response_headers) && log_headers?(:error)
|
|
47
47
|
return unless exc.respond_to?(:response_body) && exc.response_body && log_body?(:error)
|
data/lib/faraday/options.rb
CHANGED
|
@@ -186,7 +186,8 @@ module Faraday
|
|
|
186
186
|
def [](key)
|
|
187
187
|
key = key.to_sym
|
|
188
188
|
if (method = self.class.memoized_attributes[key])
|
|
189
|
-
|
|
189
|
+
value = super
|
|
190
|
+
value.nil? ? (self[key] = instance_eval(&method)) : value
|
|
190
191
|
else
|
|
191
192
|
super
|
|
192
193
|
end
|
data/lib/faraday/rack_builder.rb
CHANGED
|
@@ -73,6 +73,9 @@ module Faraday
|
|
|
73
73
|
super
|
|
74
74
|
@adapter = original.adapter
|
|
75
75
|
@handlers = original.handlers.dup
|
|
76
|
+
# @app is memoized on the first request. Drop it so the duplicate
|
|
77
|
+
# rebuilds from the copied handlers and middleware added after dup runs.
|
|
78
|
+
@app = nil
|
|
76
79
|
end
|
|
77
80
|
|
|
78
81
|
def build
|
|
@@ -20,7 +20,13 @@ module Faraday
|
|
|
20
20
|
}.freeze
|
|
21
21
|
# rubocop:enable Naming/ConstantName
|
|
22
22
|
|
|
23
|
-
DEFAULT_OPTIONS = { include_request: true, allowed_statuses: [] }.freeze
|
|
23
|
+
DEFAULT_OPTIONS = { include_request: true, allowed_statuses: [].freeze }.freeze
|
|
24
|
+
|
|
25
|
+
def on_request(env)
|
|
26
|
+
# Announce the opt-out on the shared Env so that errors raised by other
|
|
27
|
+
# middlewares (e.g. ParsingError from the Json middleware) honor it too.
|
|
28
|
+
env[:include_request] = false unless options[:include_request]
|
|
29
|
+
end
|
|
24
30
|
|
|
25
31
|
def on_complete(env)
|
|
26
32
|
return if Array(options[:allowed_statuses]).include?(env[:status])
|
data/lib/faraday/response.rb
CHANGED
|
@@ -54,7 +54,9 @@ module Faraday
|
|
|
54
54
|
raise 'response already finished' if finished?
|
|
55
55
|
|
|
56
56
|
@env = env.is_a?(Env) ? env : Env.from(env)
|
|
57
|
-
|
|
57
|
+
callbacks = @on_complete_callbacks
|
|
58
|
+
@on_complete_callbacks = []
|
|
59
|
+
callbacks.each { |callback| callback.call(@env) }
|
|
58
60
|
self
|
|
59
61
|
end
|
|
60
62
|
|
|
@@ -76,7 +78,8 @@ module Faraday
|
|
|
76
78
|
end
|
|
77
79
|
|
|
78
80
|
def marshal_load(env)
|
|
79
|
-
@env = Env.from(env)
|
|
81
|
+
@env = Env.from(env) if env
|
|
82
|
+
@on_complete_callbacks = []
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
# Expand the env with more properties, without overriding existing ones.
|
|
@@ -38,13 +38,11 @@ module Faraday
|
|
|
38
38
|
|
|
39
39
|
# symbol -> string mapper + cache
|
|
40
40
|
KeyMap = Hash.new do |map, key|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.join('-') # => "User-Agent"
|
|
47
|
-
end
|
|
41
|
+
next key if key.respond_to?(:to_str)
|
|
42
|
+
|
|
43
|
+
value = key.to_s.split('_') # user_agent: %w(user agent)
|
|
44
|
+
.each(&:capitalize!) # => %w(User Agent)
|
|
45
|
+
.join('-') # => "User-Agent"
|
|
48
46
|
keymap_mutex.synchronize { map[key] = value }
|
|
49
47
|
end
|
|
50
48
|
KeyMap[:etag] = 'ETag'
|
|
@@ -62,6 +60,8 @@ module Faraday
|
|
|
62
60
|
super
|
|
63
61
|
end
|
|
64
62
|
|
|
63
|
+
alias store []=
|
|
64
|
+
|
|
65
65
|
def fetch(key, ...)
|
|
66
66
|
key = KeyMap[key]
|
|
67
67
|
key = @names.fetch(key.downcase, key)
|
|
@@ -77,6 +77,28 @@ module Faraday
|
|
|
77
77
|
super
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
def clear
|
|
81
|
+
@names.clear
|
|
82
|
+
super
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def shift = rebuilding_names { super }
|
|
86
|
+
def compact! = rebuilding_names { super }
|
|
87
|
+
def rehash = rebuilding_names { super }
|
|
88
|
+
|
|
89
|
+
# Without a block these return an enumerator that calls back into the
|
|
90
|
+
# method once iterated, so the index only needs rebuilding in that call.
|
|
91
|
+
def delete_if = block_given? ? rebuilding_names { super } : super
|
|
92
|
+
def keep_if = block_given? ? rebuilding_names { super } : super
|
|
93
|
+
def reject! = block_given? ? rebuilding_names { super } : super
|
|
94
|
+
def select! = block_given? ? rebuilding_names { super } : super
|
|
95
|
+
alias filter! select!
|
|
96
|
+
|
|
97
|
+
# A mapping hash is a valid alternative to a block.
|
|
98
|
+
def transform_keys!(*args, &block)
|
|
99
|
+
args.empty? && !block ? super : rebuilding_names { super }
|
|
100
|
+
end
|
|
101
|
+
|
|
80
102
|
def dig(key, *rest)
|
|
81
103
|
key = KeyMap[key]
|
|
82
104
|
key = @names.fetch(key.downcase, key)
|
|
@@ -136,6 +158,18 @@ module Faraday
|
|
|
136
158
|
|
|
137
159
|
private
|
|
138
160
|
|
|
161
|
+
# Runs the block, then rebuilds the name index even if the block raised
|
|
162
|
+
# or exited early, as the underlying Hash may already be partially mutated.
|
|
163
|
+
def rebuilding_names
|
|
164
|
+
yield
|
|
165
|
+
ensure
|
|
166
|
+
rebuild_names
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def rebuild_names
|
|
170
|
+
@names = each_key.to_h { |key| [KeyMap[key].downcase, key] }
|
|
171
|
+
end
|
|
172
|
+
|
|
139
173
|
# Join multiple values with a comma.
|
|
140
174
|
def add_parsed(key, value)
|
|
141
175
|
if key?(key)
|
data/lib/faraday/utils.rb
CHANGED
|
@@ -27,10 +27,22 @@ module Faraday
|
|
|
27
27
|
|
|
28
28
|
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
|
|
29
29
|
|
|
30
|
+
# CGI.escape works on raw bytes: it matches ESCAPE_RE only where no multibyte character
|
|
31
|
+
# hides an ASCII byte (Shift_JIS 'ア' is "\x83A"), unlike ESCAPE_RE it doesn't raise on
|
|
32
|
+
# invalid bytes, and it returns String subclasses (SafeBuffer) as is when nothing is escaped.
|
|
33
|
+
CGI_ESCAPE_ENCODINGS = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::BINARY].freeze
|
|
34
|
+
private_constant :CGI_ESCAPE_ENCODINGS
|
|
35
|
+
|
|
30
36
|
def escape(str)
|
|
31
|
-
str.to_s
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
str = str.to_s
|
|
38
|
+
if str.instance_of?(String) && CGI_ESCAPE_ENCODINGS.include?(str.encoding) && str.valid_encoding?
|
|
39
|
+
escaped = CGI.escape(str)
|
|
40
|
+
default_space_encoding == '+' ? escaped : escaped.gsub('+', default_space_encoding)
|
|
41
|
+
else
|
|
42
|
+
str.gsub(ESCAPE_RE) do |match|
|
|
43
|
+
"%#{match.unpack('H2' * match.bytesize).join('%').upcase}"
|
|
44
|
+
end.gsub(' ', default_space_encoding)
|
|
45
|
+
end
|
|
34
46
|
end
|
|
35
47
|
|
|
36
48
|
def unescape(str)
|
data/lib/faraday/version.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require 'webmock/rspec'
|
|
4
4
|
WebMock.disable_net_connect!(allow_localhost: true)
|
|
5
5
|
|
|
6
|
+
# The gem ships only `spec/external_adapters` and `spec/support` (see `spec.files` in
|
|
7
|
+
# faraday.gemspec). Requiring a file from anywhere else means updating the gemspec too.
|
|
6
8
|
require_relative '../support/helper_methods'
|
|
7
9
|
require_relative '../support/disabling_stub'
|
|
8
10
|
require_relative '../support/streaming_response_checker'
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
|
|
5
|
+
module Faraday
|
|
6
|
+
module IntegrationHelpers
|
|
7
|
+
# Throwaway certificate authority used by the SSL and proxy integration
|
|
8
|
+
# specs. Everything is generated in memory, so no certificate files need to
|
|
9
|
+
# be committed or generated by the CI workflow.
|
|
10
|
+
class TestPKI
|
|
11
|
+
Credentials = Struct.new(:cert, :key)
|
|
12
|
+
|
|
13
|
+
attr_reader :ca_cert, :ca_key
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@ca_key = OpenSSL::PKey::RSA.new(2048)
|
|
17
|
+
@ca_cert = build_certificate('Faraday Test CA', ca_key, ca_key, nil) do |cert, factory|
|
|
18
|
+
[
|
|
19
|
+
factory.create_extension('basicConstraints', 'CA:TRUE', true),
|
|
20
|
+
factory.create_extension('keyUsage', 'keyCertSign,cRLSign', true),
|
|
21
|
+
factory.create_extension('subjectKeyIdentifier', 'hash', false)
|
|
22
|
+
].each { |ext| cert.add_extension(ext) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Issues a certificate signed by this CA.
|
|
27
|
+
#
|
|
28
|
+
# @param common_name [String] certificate subject CN
|
|
29
|
+
# @param dns_names [Array<String>] DNS subjectAltNames
|
|
30
|
+
# @param ip_addresses [Array<String>] IP subjectAltNames
|
|
31
|
+
# @return [Credentials]
|
|
32
|
+
def issue(common_name, dns_names: [], ip_addresses: [])
|
|
33
|
+
key = OpenSSL::PKey::RSA.new(2048)
|
|
34
|
+
sans = dns_names.map { |name| "DNS:#{name}" } + ip_addresses.map { |ip| "IP:#{ip}" }
|
|
35
|
+
|
|
36
|
+
cert = build_certificate(common_name, key, ca_key, ca_cert) do |built, factory|
|
|
37
|
+
built.add_extension(factory.create_extension('basicConstraints', 'CA:FALSE', true))
|
|
38
|
+
built.add_extension(factory.create_extension('subjectAltName', sans.join(','), false)) if sans.any?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
Credentials.new(cert, key)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Writes the CA certificate to disk, as expected by the `ca_file` option.
|
|
45
|
+
def write_ca_file(path)
|
|
46
|
+
File.write(path, ca_cert.to_pem)
|
|
47
|
+
path
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [OpenSSL::X509::Store] a store that only trusts this CA
|
|
51
|
+
def cert_store
|
|
52
|
+
OpenSSL::X509::Store.new.tap { |store| store.add_cert(ca_cert) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def build_certificate(common_name, key, signing_key, issuer_cert)
|
|
58
|
+
cert = OpenSSL::X509::Certificate.new
|
|
59
|
+
cert.version = 2
|
|
60
|
+
cert.serial = rand(1..(2**32))
|
|
61
|
+
cert.subject = OpenSSL::X509::Name.parse("/CN=#{common_name}")
|
|
62
|
+
cert.issuer = issuer_cert ? issuer_cert.subject : cert.subject
|
|
63
|
+
cert.public_key = key.public_key
|
|
64
|
+
cert.not_before = Time.now - 60
|
|
65
|
+
cert.not_after = Time.now + 3600
|
|
66
|
+
|
|
67
|
+
factory = OpenSSL::X509::ExtensionFactory.new
|
|
68
|
+
factory.subject_certificate = cert
|
|
69
|
+
factory.issuer_certificate = issuer_cert || cert
|
|
70
|
+
yield cert, factory
|
|
71
|
+
|
|
72
|
+
cert.sign(signing_key, OpenSSL::Digest.new('SHA256'))
|
|
73
|
+
cert
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'socket'
|
|
6
|
+
|
|
7
|
+
module Faraday
|
|
8
|
+
module IntegrationHelpers
|
|
9
|
+
# Minimal HTTPS server running in a background thread, used to exercise
|
|
10
|
+
# SSLOptions and ProxyOptions against real TLS handshakes.
|
|
11
|
+
#
|
|
12
|
+
# Every request is answered with a JSON document describing what the server
|
|
13
|
+
# saw, e.g. `{"path":"/","client_cert_subject":null,"remote_ip":"127.0.0.1"}`.
|
|
14
|
+
class TLSServer
|
|
15
|
+
attr_reader :port
|
|
16
|
+
|
|
17
|
+
# @param credentials [TestPKI::Credentials] server certificate and key
|
|
18
|
+
# @param client_ca [TestPKI, nil] when given, clients must present a
|
|
19
|
+
# certificate signed by this CA (mutual TLS)
|
|
20
|
+
# @param host [String] address to listen on; '0.0.0.0' makes the server
|
|
21
|
+
# reachable from service containers, such as the proxies
|
|
22
|
+
def initialize(credentials, client_ca: nil, host: '127.0.0.1')
|
|
23
|
+
@tcp_server = TCPServer.new(host, 0)
|
|
24
|
+
@port = @tcp_server.addr[1]
|
|
25
|
+
@ssl_server = OpenSSL::SSL::SSLServer.new(@tcp_server, ssl_context(credentials, client_ca))
|
|
26
|
+
@ssl_server.start_immediately = false
|
|
27
|
+
@threads = []
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.start(...)
|
|
31
|
+
new(...).start
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def start
|
|
35
|
+
@acceptor = Thread.new do
|
|
36
|
+
loop do
|
|
37
|
+
socket = @ssl_server.accept
|
|
38
|
+
@threads << Thread.new { handle(socket) }
|
|
39
|
+
end
|
|
40
|
+
rescue IOError, Errno::EBADF
|
|
41
|
+
nil # server stopped
|
|
42
|
+
end
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def stop
|
|
47
|
+
@ssl_server.close
|
|
48
|
+
@acceptor&.join(1)
|
|
49
|
+
@threads.each { |thread| thread.join(1) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def url(host = 'localhost')
|
|
53
|
+
"https://#{host}:#{port}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def ssl_context(credentials, client_ca)
|
|
59
|
+
OpenSSL::SSL::SSLContext.new.tap do |ctx|
|
|
60
|
+
ctx.cert = credentials.cert
|
|
61
|
+
ctx.key = credentials.key
|
|
62
|
+
if client_ca
|
|
63
|
+
ctx.cert_store = client_ca.cert_store
|
|
64
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def handle(socket)
|
|
70
|
+
socket.accept
|
|
71
|
+
request_line = socket.gets
|
|
72
|
+
while (line = socket.gets)
|
|
73
|
+
break if line.strip.empty?
|
|
74
|
+
end
|
|
75
|
+
respond(socket, request_line.to_s.split[1])
|
|
76
|
+
rescue OpenSSL::SSL::SSLError, SystemCallError, IOError
|
|
77
|
+
nil # the client rejected the handshake or went away
|
|
78
|
+
ensure
|
|
79
|
+
socket.close unless socket.closed?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def respond(socket, path)
|
|
83
|
+
body = JSON.generate(path: path, client_cert_subject: socket.peer_cert&.subject&.to_s,
|
|
84
|
+
remote_ip: socket.io.remote_address.ip_address)
|
|
85
|
+
socket.write("HTTP/1.1 200 OK\r\n" \
|
|
86
|
+
"Content-Type: application/json\r\n" \
|
|
87
|
+
"Content-Length: #{body.bytesize}\r\n" \
|
|
88
|
+
"Connection: close\r\n\r\n#{body}")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Integration test configuration and helpers
|
|
4
|
+
#
|
|
5
|
+
# This module provides configuration and helpers for integration tests that run
|
|
6
|
+
# against real HTTP services in GitHub Actions service containers.
|
|
7
|
+
#
|
|
8
|
+
# Service URLs are accessed by service name in container jobs (no port mapping needed).
|
|
9
|
+
# See .github/workflows/integration-tests.yml for service configuration.
|
|
10
|
+
|
|
11
|
+
module Faraday
|
|
12
|
+
module IntegrationHelpers
|
|
13
|
+
# Service URLs for GitHub Actions service containers
|
|
14
|
+
# These are accessed by service name when running in container jobs
|
|
15
|
+
HTTPBIN_URL = ENV.fetch('HTTPBIN_URL', 'http://httpbin')
|
|
16
|
+
# Forward proxies (mitmdump); the second one requires Basic credentials
|
|
17
|
+
PROXY_URL = ENV.fetch('PROXY_URL', 'http://proxy:8080')
|
|
18
|
+
AUTH_PROXY_URL = ENV.fetch('AUTH_PROXY_URL', 'http://auth-proxy:8080')
|
|
19
|
+
|
|
20
|
+
# Create a direct Faraday connection to httpbin (no proxy)
|
|
21
|
+
#
|
|
22
|
+
# @param options [Hash] Connection options (merged with defaults)
|
|
23
|
+
# @yield [builder] Middleware stack configuration
|
|
24
|
+
# @return [Faraday::Connection]
|
|
25
|
+
def httpbin_connection(options = {}, &block)
|
|
26
|
+
Faraday.new(HTTPBIN_URL, options, &block)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Generic helper to create a Faraday connection for integration tests
|
|
30
|
+
#
|
|
31
|
+
# @param url [String] Base URL for the connection
|
|
32
|
+
# @param options [Hash] Connection options
|
|
33
|
+
# @yield [builder] Middleware stack configuration
|
|
34
|
+
# @return [Faraday::Connection]
|
|
35
|
+
def create_integration_connection(url, options = {}, &block)
|
|
36
|
+
Faraday.new(url, options, &block)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Configure RSpec for integration tests
|
|
42
|
+
RSpec.configure do |config|
|
|
43
|
+
# Include integration helpers in integration tests
|
|
44
|
+
config.include Faraday::IntegrationHelpers, type: :integration
|
|
45
|
+
|
|
46
|
+
# Tag all integration tests for easy filtering
|
|
47
|
+
# Run with: bundle exec rspec --tag integration
|
|
48
|
+
# Skip with: bundle exec rspec --tag ~integration
|
|
49
|
+
config.define_derived_metadata(file_path: %r{/spec/integration/}) do |metadata|
|
|
50
|
+
metadata[:type] = :integration
|
|
51
|
+
metadata[:integration] = true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Integration tests may take longer than unit tests
|
|
55
|
+
config.around(:each, type: :integration) do |example|
|
|
56
|
+
# Completely disable WebMock for integration tests
|
|
57
|
+
# (WebMock can interfere with streaming even when "allowed")
|
|
58
|
+
WebMock.disable!
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
# Set a reasonable timeout for integration tests (30 seconds)
|
|
62
|
+
Timeout.timeout(30) do
|
|
63
|
+
example.run
|
|
64
|
+
end
|
|
65
|
+
ensure
|
|
66
|
+
# Re-enable WebMock for unit tests
|
|
67
|
+
WebMock.enable!
|
|
68
|
+
WebMock.disable_net_connect!(allow_localhost: true)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: faraday
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.15.
|
|
4
|
+
version: 2.15.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- "@technoweenie"
|
|
@@ -104,37 +104,13 @@ files:
|
|
|
104
104
|
- lib/faraday/utils/params_hash.rb
|
|
105
105
|
- lib/faraday/version.rb
|
|
106
106
|
- spec/external_adapters/faraday_specs_setup.rb
|
|
107
|
-
- spec/faraday/adapter/test_spec.rb
|
|
108
|
-
- spec/faraday/adapter_registry_spec.rb
|
|
109
|
-
- spec/faraday/adapter_spec.rb
|
|
110
|
-
- spec/faraday/connection_spec.rb
|
|
111
|
-
- spec/faraday/error_spec.rb
|
|
112
|
-
- spec/faraday/middleware_registry_spec.rb
|
|
113
|
-
- spec/faraday/middleware_spec.rb
|
|
114
|
-
- spec/faraday/options/env_spec.rb
|
|
115
|
-
- spec/faraday/options/options_spec.rb
|
|
116
|
-
- spec/faraday/options/proxy_options_spec.rb
|
|
117
|
-
- spec/faraday/options/request_options_spec.rb
|
|
118
|
-
- spec/faraday/params_encoders/flat_spec.rb
|
|
119
|
-
- spec/faraday/params_encoders/nested_spec.rb
|
|
120
|
-
- spec/faraday/rack_builder_spec.rb
|
|
121
|
-
- spec/faraday/request/authorization_spec.rb
|
|
122
|
-
- spec/faraday/request/instrumentation_spec.rb
|
|
123
|
-
- spec/faraday/request/json_spec.rb
|
|
124
|
-
- spec/faraday/request/url_encoded_spec.rb
|
|
125
|
-
- spec/faraday/request_spec.rb
|
|
126
|
-
- spec/faraday/response/json_spec.rb
|
|
127
|
-
- spec/faraday/response/logger_spec.rb
|
|
128
|
-
- spec/faraday/response/raise_error_spec.rb
|
|
129
|
-
- spec/faraday/response_spec.rb
|
|
130
|
-
- spec/faraday/utils/headers_spec.rb
|
|
131
|
-
- spec/faraday/utils_spec.rb
|
|
132
|
-
- spec/faraday_spec.rb
|
|
133
|
-
- spec/spec_helper.rb
|
|
134
107
|
- spec/support/disabling_stub.rb
|
|
135
108
|
- spec/support/fake_safe_buffer.rb
|
|
136
109
|
- spec/support/faraday_middleware_subclasses.rb
|
|
137
110
|
- spec/support/helper_methods.rb
|
|
111
|
+
- spec/support/integration/test_pki.rb
|
|
112
|
+
- spec/support/integration/tls_server.rb
|
|
113
|
+
- spec/support/integration_helper.rb
|
|
138
114
|
- spec/support/shared_examples/adapter.rb
|
|
139
115
|
- spec/support/shared_examples/params_encoder.rb
|
|
140
116
|
- spec/support/shared_examples/request_method.rb
|
|
@@ -144,7 +120,7 @@ licenses:
|
|
|
144
120
|
- MIT
|
|
145
121
|
metadata:
|
|
146
122
|
homepage_uri: https://lostisland.github.io/faraday
|
|
147
|
-
changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.15.
|
|
123
|
+
changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.15.1
|
|
148
124
|
source_code_uri: https://github.com/lostisland/faraday
|
|
149
125
|
bug_tracker_uri: https://github.com/lostisland/faraday/issues
|
|
150
126
|
rubygems_mfa_required: 'true'
|