Compare commits

..

11 Commits

Author SHA1 Message Date
HoneyryderChuck
8652d1978f bump version to 0.22.5 2023-03-29 00:33:27 +01:00
HoneyryderChuck
5532d8eb73 Merge branch 'errors-native-resolver' into 'master'
happy eyeballs + connection handling fixes

Closes #228

See merge request os85/httpx!241
2023-03-28 23:22:09 +00:00
HoneyryderChuck
ea61cce815 print ruby version in tests 2023-03-29 00:12:19 +01:00
HoneyryderChuck
64903fca4e addded happy eyeballs example, since we can't add tests for it in the CI 2023-03-28 23:56:59 +01:00
HoneyryderChuck
b07117e16e treat tls errors as a connection error which HE2 should handle 2023-03-28 23:56:34 +01:00
HoneyryderChuck
487cac6eef do not test ruby 2.1 and 2.2 in CI
can't build the required docker imaged anymore...
2023-03-28 23:41:37 +01:00
HoneyryderChuck
499a87a8f1 fix sentry call 2023-03-28 02:01:53 +01:00
HoneyryderChuck
f744ae651b force "connect error" path on resolve errors for happy eyeballs
without this, requests may not get merged between connections, and
callbacks aren't called.

multi resolver path gets simplified by this change, given that the
callbacks handle the bulk of happy eyeballs complexity.
2023-03-28 01:50:25 +01:00
HoneyryderChuck
1b0327261f do not rely on send being called just once
the sentry and datadog plugins have been wrongly relying on the
assumption that #send is called just once, when in fact, it can be
called multiple times, both for conn exhaustion, as well as conn merging
(coalescing + happy eyeballs) scenarios.

Because of this, their "on response" callback could be set multiple times, which was confusing. So this fixes the behaviour.

Fixes #228
2023-03-28 01:40:34 +01:00
HoneyryderChuck
707e653883 using callbacks_for? on happy eyeballs error detection path 2023-03-28 01:39:41 +01:00
HoneyryderChuck
73f0d609b0 fix datadog tests, both gem version compare and error type
error type has been wrongly tested sofar. This commit fixes it.
2023-03-28 01:37:06 +01:00
12 changed files with 67 additions and 35 deletions

View File

@ -43,16 +43,6 @@ test jruby:
script:
./spec.sh jruby 9.0.0.0
allow_failure: true
test ruby 2/1:
<<: *test_settings
script:
./spec.sh ruby 2.1
test ruby 2/2:
<<: *test_settings
only:
- master
script:
./spec.sh ruby 2.2
test ruby 2/3:
<<: *test_settings
script:

View File

@ -0,0 +1,6 @@
# 0.22.5
## Bugfixes
* `datadog` and `sentry` integrations did not account for `Connection#send` being possibly called multiple times (something possible for connection coalescing, max requests exhaustion, or Happy Eyeballs 2), and were registering multiple `on(:response)` callbacks. Requests are now marked when decorated the first time.
* Happy Eyeballs handshake "connect errors" routine is now taking both name resolution errors, as well as TLS handshake errors, into account, when the handshake fails.

View File

@ -0,0 +1,20 @@
#
# From https://ipv6friday.org/blog/2012/11/happy-testing/
#
# The http server accessible via the test doamins is returning empty responses.
# If you want to verify that the correct IP family is being used to establish the connection,
# set HTTPX_DEBUG=2
#
require "httpx"
URLS = %w[http://badipv4.test.ipv6friday.org/] * 1
# URLS = %w[http://badipv6.test.ipv6friday.org/] * 1
responses = HTTPX.get(*URLS, ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE})
# responses = HTTPX.get(*URLS)
Array(responses).each(&:raise_for_status)
puts "Status: \n"
puts Array(responses).map(&:status)
puts "Payload: \n"
puts Array(responses).map(&:to_s)

View File

@ -198,14 +198,14 @@ class DatadogTest < Minitest::Test
assert span.get_tag("http.method") == verb
assert span.get_tag("http.url") == uri.path
error_tag = if defined?(::DDTrace) && ::DDTrace::VERSION::STRING >= "1.8.0"
error_tag = if defined?(::DDTrace) && Gem::Version.new(::DDTrace::VERSION::STRING) >= Gem::Version.new("1.8.0")
"error.message"
else
"error.msg"
end
if error
assert span.get_tag("error.type") == error
assert span.get_tag("error.type") == "HTTPX::NativeResolveError"
assert !span.get_tag(error_tag).nil?
assert span.status == 1
elsif response.status >= 400
@ -241,7 +241,7 @@ class DatadogTest < Minitest::Test
assert span.get_metric("_dd1.sr.eausr") == sample_rate
end
if defined?(::DDTrace) && ::DDTrace::VERSION::STRING >= "1.0.0"
if defined?(::DDTrace) && Gem::Version.new(::DDTrace::VERSION::STRING) >= Gem::Version.new("1.0.0")
def set_datadog(options = {}, &blk)
Datadog.configure do |c|
@ -305,7 +305,7 @@ class DatadogTest < Minitest::Test
# Retrieves and sorts all spans in the current tracer instance.
# This method does not cache its results.
def fetch_spans
spans = if defined?(::DDTrace) && ::DDTrace::VERSION::STRING >= "1.0.0"
spans = if defined?(::DDTrace) && Gem::Version.new(::DDTrace::VERSION::STRING) >= Gem::Version.new("1.0.0")
(tracer.instance_variable_get(:@traces) || []).map(&:spans)
else
tracer.instance_variable_get(:@spans) || []

View File

@ -158,9 +158,19 @@ module TRACING_MODULE # rubocop:disable Naming/ClassAndModuleCamelCase
end
end
module RequestMethods
def __datadog_enable_trace!
return super if @__datadog_enable_trace
RequestTracer.new(self).call
@__datadog_enable_trace = true
end
end
module ConnectionMethods
def send(request)
RequestTracer.new(request).call
request.__datadog_enable_trace!
super
end
end

View File

@ -89,9 +89,19 @@ module HTTPX::Plugins
end
end
module RequestMethods
def __sentry_enable_trace!
return super if @__sentry_enable_trace
Tracer.call(self)
@__sentry_enable_trace = true
end
end
module ConnectionMethods
def send(request)
Tracer.call(request)
request.__sentry_enable_trace!
super
end
end

View File

@ -22,12 +22,12 @@ module HTTPX
callbacks(type).delete_if { |pr| :delete == pr.call(*args) } # rubocop:disable Style/YodaCondition
end
protected
def callbacks_for?(type)
@callbacks.key?(type) && !@callbacks[type].empty?
@callbacks.key?(type) && @callbacks[type].any?
end
protected
def callbacks(type = nil)
return @callbacks unless type

View File

@ -538,12 +538,13 @@ module HTTPX
# connect errors, exit gracefully
error = ConnectionError.new(e.message)
error.set_backtrace(e.backtrace)
connecting? && callbacks(:connect_error).any? ? emit(:connect_error, error) : handle_error(error)
connecting? && callbacks_for?(:connect_error) ? emit(:connect_error, error) : handle_error(error)
@state = :closed
emit(:close)
rescue TLSError => e
# connect errors, exit gracefully
handle_error(e)
connecting? && callbacks_for?(:connect_error) ? emit(:connect_error, e) : handle_error(e)
@state = :closed
emit(:close)
end

View File

@ -141,8 +141,9 @@ module HTTPX
connection.once(:connect_error) do |err|
if new_connection.connecting?
new_connection.merge(connection)
connection.force_reset
else
connection.handle_error(err)
connection.__send__(:handle_error, err)
end
end
@ -156,8 +157,9 @@ module HTTPX
if connection.connecting?
# main connection has the requests
connection.merge(new_connection)
new_connection.force_reset
else
new_connection.handle_error(err)
new_connection.__send__(:handle_error, err)
end
end
@ -183,6 +185,8 @@ module HTTPX
end
def on_resolver_error(connection, error)
return connection.emit(:connect_error, error) if connection.connecting? && connection.callbacks_for?(:connect_error)
connection.emit(:error, error)
end

View File

@ -64,12 +64,7 @@ module HTTPX
end
def on_resolver_error(connection, error)
@errors[connection] << error
return unless @errors[connection].size >= @resolvers.size
errors = @errors.delete(connection)
emit(:error, connection, errors.first)
emit(:error, connection, error)
end
def on_resolver_close(resolver)

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true
module HTTPX
VERSION = "0.22.4"
VERSION = "0.22.5"
end

View File

@ -5,6 +5,8 @@ set -e
export LANG=C.UTF-8
export LANGUAGE=C.UTF-8
ruby --version
RUBY_PLATFORM=`ruby -e 'puts RUBY_PLATFORM'`
RUBY_ENGINE=`ruby -e 'puts RUBY_ENGINE'`
IPTABLES=iptables-translate
@ -13,12 +15,6 @@ if [[ "$RUBY_ENGINE" = "truffleruby" ]]; then
dnf install -y iptables iproute which file idn2 git xz
elif [[ "$RUBY_PLATFORM" = "java" ]]; then
apt-get update && apt-get install -y build-essential iptables iproute2 file idn2 git
elif [[ ${RUBY_VERSION:0:3} = "2.1" ]]; then
apt-get update && apt-get install -y --force-yes libsodium-dev iptables iproute2 libmagic-dev shared-mime-info
IPTABLES=iptables
elif [[ ${RUBY_VERSION:0:3} = "2.2" ]]; then
apt-get update && apt-get install -y --force-yes iptables iproute2 libmagic-dev shared-mime-info
IPTABLES=iptables
elif [[ ${RUBY_VERSION:0:3} = "2.3" ]]; then
# installing custom openssl
apt-get update && apt-get install -y iptables iproute2 iptables-nftables-compat libmagic-dev shared-mime-info # openssl=1.0.2l openssl-dev=1.0.2l