Skip to content

Add pluggable curb/Faraday HTTP client backends - #32

Closed
rubyjedi wants to merge 65 commits into
masterfrom
feature/curb-faraday-http-backends
Closed

Add pluggable curb/Faraday HTTP client backends#32
rubyjedi wants to merge 65 commits into
masterfrom
feature/curb-faraday-http-backends

Conversation

@rubyjedi

@rubyjedi rubyjedi commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Summary

  • Makes the HTTP client backend pluggable (SOAP4R_HTTP_CLIENTS, mirroring the existing SOAP4R_PARSERS mechanism), adding curb and Faraday (typhoeus adapter) as opt-in backends alongside the existing httpclient/net_http.
  • Broadens test coverage that used to be httpclient-only (backend-neutral wiredump parsing, SSL config coverage across all SSL-capable backends), fixing several real bugs surfaced in the process (Faraday :ciphers SSLOptions crash, URI.parse vs. already-a-URI object, curb Curl::Easy#ciphers= not existing, Tempfile finalizer bugs, Ruby 1.8.7 syntax regressions).
  • Gates curb (>= 2.4, libcurl version requirement) and Faraday (>= 2.6, an enhancement for modern Rubies rather than a legacy capability — older Faraday releases don't match this project's adapter assumptions) to their real floors, with CI skipping cleanly below those floors instead of hard-failing.
  • Fixes a latent CI bug where bundle config set with "..." (Bundler 2.x-only syntax) silently no-opped on the Bundler 1.17.3 every Ruby below 3.2 falls back to — curb/faraday were never actually being exercised in CI on those versions. Replaced with BUNDLE_WITH.
  • Corrects an Ox dependency issue found via full-matrix regression testing: Ruby 2.2.x–2.6.x resolve Ox 2.14.14 unconstrained (Ox's own gemspec added a hard Ruby >= 2.7 floor starting at 2.14.15), and 2.14.14 segfaults inside Ox.sax_parse's :convert_special => true path on complex documents. htmlentities avoids that path entirely and is restored as a required dependency for that range; Ruby 1.9.3–2.1.x pins Ox exactly to 2.14.6 (decodes entities natively, no crash); Ruby 1.8.7 stays on the older Ox 2.4.5 (2.14.6 fails to build there).
  • Adds CHANGELOG.md holding the full investigation detail (version-by-version Ox testing, Faraday's architecture mismatch, the BUNDLE_WITH bug, CI's Docker-per-version architecture, full known-test-exceptions writeup) that had accumulated as long inline comments in Gemfile, ci.yml, and README.md — those three files now carry short pointers instead.
  • Scopes curb's already-documented libcurl/OpenSSL SSL exception on Ruby 2.4.10/2.5.9 to continue-on-error on just that step, instead of failing the whole job.

Test plan

  • Full regression matrix (all 5 XML parsers × all applicable HTTP backends) re-run clean across 1.8.7, 1.9.3, 2.0.0, 2.1.10, 2.2.10, 2.3.8, 2.6.10 — all green except 1.8.7, which shows only its pre-existing, already-documented exceptions (see "Known Test Suite Exceptions" in README.md).
  • ci.yml validated as syntactically correct YAML; Gemfile/lib/xsd/xmlparser/oxparser.rb validated as syntactically correct Ruby.
  • GitHub Actions CI run on this PR — green aside from the pre-existing, documented exceptions (1.8.7, JRuby, 3.0.7/3.1.7/3.2.11, and now 2.4.10/2.5.9's curb step).

razum2um and others added 30 commits June 1, 2018 17:13
Removed legacy dependencies on httpclient and logger; removed deprecated rdoc reference in gemspec.
Non-breaking change.

Adds `options = {}` argument to `XSD::Mapping::Mapper#obj2xml` and
`XSD::Mapping::Mapper#xml2obj` instance methods and `XSD::Mapping.obj2xml` and
`XSD::Mapping.xml2obj` module methods.

Currently, XML namespaces cannot be customized because the options for the
`SOAP::Generator` constructor are not exposed as an argument of the
`XSD::Mapping::Mapper#obj2xml` instance method and/or the `XSD::Mapping.obj2xml`
module method.

This commit enables the customization of XML namespaces. For example, to set the
default XML namespace:

```ruby
mapper.obj2xml(obj, nil, nil, { # indented for readability
  default_ns: SOAP::NS.new({
    "ex" => "http://example.com/ns#",
  }),
})
```
Ruby 3.2 introduced a deprecation of the third positional parameter for
encoding:
ruby/ruby@7e8fa06

The argument was then removed in Ruby 3.3:
ruby/ruby@04cfb26

```
ArgumentError: wrong number of arguments (given 3, expected 1..2) (ArgumentError)

  USASCIIRegexp = Regexp.new("\\A#{us_ascii}*\\z", nil, 'n')
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
Fix Regexp.new ArgumentError on ruby 3.3
According to https://bugs.ruby-lang.org/issues/16131, untaint is a no-op on all versions of ruby >= 2.7 and is completely removed on >= ruby 3.2.

Use the pattern established at https://github.com/ruby/reline/pull/61/files to only call this method when it’s relevant.
only call untaint on ruby < 2.7
…ions

Add "options" Argument to XSD Mapper/Mapping Methods
rubyjedi and others added 25 commits July 7, 2026 12:27
Brings this 2018-era branch up to date with all subsequent upstream
work so its group-support fix can be tested against, and merged into,
the current codebase.
Marek Veber's fix (previous commit) handles a <xsd:group ref="..."/>
used as a complexType's sole, direct content -- as opposed to
groupele_type's existing coverage, where the group ref sits nested
inside an explicit <xsd:sequence>. Before the fix, generating a class
definition for a complexType shaped this way crashed with "undefined
method 'have_any?' for an instance of WSDL::XMLSchema::Group", since
Group didn't yet have the ComplexType-derived methods (elements,
have_any?, attributes) needed once it's asked to stand in directly for
a complexType's content.

This is valid, spec-conformant XSD, not an edge case: the W3C XML
Schema structures grammar lists group/all/choice/sequence as parallel
alternatives for a complexType's direct content, and
lib/wsdl/xmlSchema/complexType.rb's own parse_element (written by the
original upstream author in 2007, predating this fork) already parses
a bare group ref at that position identically to how it parses a bare
sequence or choice -- the gap was purely downstream, in code
generation never being wired up to match.

Adds groupdirect_type to test/wsdl/group/group.wsdl, regenerates the
expected classdef/mapping-registry fixtures to match, and adds a
dedicated test_groupdirect_classdef assertion. expectedDriver.rb is
unaffected since no new operation/message was added -- this is a
schema-only regression test, verified via WSDL::SOAP::WSDL2Ruby's own
class-definition and mapping-registry generation paths, both of which
previously crashed on this shape without Marek's fix.

Also tidies up the #private comment left over from that fix into a
real explanatory comment, now that refelement is genuinely public
(needed by classDefCreator.rb and mappingRegistryCreatorSupport.rb to
resolve a group ref's actual definition).

Co-Authored-By: Marek Veber <marek.veber@solnet.cz>
Prep for an upcoming release covering the xsd:group-as-direct-content
fix (PR #19). Holding off on tagging/releasing until the other pending
PR (#16, proxy auth) is also reviewed and merged.
Adds support for a <xsd:group ref="..."/> used as a complexType's sole, direct content (e.g. `<xsd:complexType><xsd:group ref="x"/></xsd:complexType>`) -- valid, spec-conformant XSD that previously crashed class-definition generation with "undefined method 'have_any?' for an instance of WSDL::XMLSchema::Group".

Thanks @marek-veber for the original fix! Rebased onto current master, added regression test coverage (test/wsdl/group/group.wsdl's new groupdirect_type, plus a dedicated assertion in test_rpc.rb), and bumped the version to 2.1.1 in prep for release.
Winston's fix (previous commit) passes @Proxy's user/password through
to Net::HTTP::Proxy(...), which the Net::HTTP fallback driver
(lib/soap/netHttpClient.rb) previously dropped on the floor entirely.

No existing test exercises this file at all: HTTPStreamHandler only
falls back to NetHttpClient when both httpclient and http-access2 fail
to load, and this project's Gemfile always requires httpclient, so
nothing else in the suite ever reaches this code path. Added
test/soap/test_nethttpclient.rb, calling create_connection directly
(it's private, invoked via #send) since it just builds and configures
a Net::HTTP object with no actual #start/connect call -- safe to test
without a real network call or proxy server. Covers the credentialed
case, the credential-less case (no regression), and the no-proxy-at-all
case.

Confirmed this fails on the exact credentialed-proxy assertion without
Winston's fix, and passes cleanly with it.

Co-Authored-By: Winston <winstonw@stratolab.com>
Adds HTTP_PROXY username/password support to the Net::HTTP fallback driver, superseding #16 (source fork deleted). Full credit to @winstonwolff for the original fix; cherry-picked with authorship preserved, plus a new regression test (test/soap/test_nethttpclient.rb) since this file previously had zero test coverage.
The existing '~> 2.4.5' constraint for Ruby < 2.2 was meant to pin ox
to the last release before it started requiring rb_utf8_str_new
(Ruby >= 2.2), but '~>' allows any 2.4.x patch release, and Bundler
resolved 2.4.13 -- which has a *different*, undocumented crash of its
own on these old Rubies: an unresolved RSTRUCT_GET symbol at native-ext
load time. Confirmed ox 2.4.5 exactly loads fine; the pin's intent was
already correct, its mechanism just wasn't tight enough to enforce it.

Found while investigating why oxparser reported "module not found" on
Ruby 1.9.3 despite ox installing successfully during bundle install.
Verified fixed: ox now resolves to exactly 2.4.5 (not 2.4.13) on 1.9.3,
and the full rake test:deep suite passes clean across all available
parsers (337 tests, 2646 assertions, 0 failures, 0 errors).
Two small, independently-verified fixes to wsdl2ruby's class generation,
found while surveying rubyjedi/soap4r's fork network for unmerged fixes.

1. classDefCreator#parse_elements collected one init_params entry per
   element without deduplicating by name, so a complexType whose content
   declares (or pulls in, via group refs) the same element name twice
   generated `def initialize(x = nil, x = nil)` -- a Ruby SyntaxError the
   moment the file was loaded. This is legal, unambiguous XSD: inside a
   <sequence> (not <choice>), particle position is fixed, so Unique
   Particle Attribution isn't violated by two same-named siblings.

   Originally found and fixed by Marcus Krejpowicz (krebbl) in
   krebbl/soap4r commit 381b9f1 ("fix duplicate initialize params
   issue"):
   krebbl@381b9f1
   Not adopted as-is here -- that fix was a plain `init_params.uniq`,
   which would silently wire both occurrences onto a single ivar,
   discarding one value instead of keeping both independently
   addressable. Renaming the second occurrence instead, the same way
   define_attribute already disambiguates colliding attribute constants.

2. define_attribute silently discarded the XSD `fixed`/`default` value
   constraints already parsed onto WSDL::XMLSchema::Attribute. Per XML
   Schema Part 1 S3.2.1, an omitted attribute with a `default` or `fixed`
   constraint is supposed to behave as if present with that value; the
   generated getter used to just return nil instead.

   Originally found and fixed by Jan-Willem Koelewijn (jwkoelewijn) in
   nedap/soap4r commit 0739995 ("Add fixed and default attributes to
   class gen"):
   nedap@0739995
   Adopted close to the original. Regenerating
   test/wsdl/group/expectedClassdef.rb and test/wsdl/ref/expectedProduct.rb
   was required: both fixtures already had default-valued attributes
   (attr_min/attr_max, yesno) whose default was silently being ignored
   before this fix.

Regression coverage added under test/wsdl/dupinitparams/ and
test/wsdl/attrdefault/.

Co-Authored-By: Marcus Krejpowicz <krebbl@gmail.com>
Co-Authored-By: jwkoelewijn <janwillem.koelewijn@nedap.com>
XML comments can't contain "--" anywhere in their body, only as the
opening/closing delimiters. My regression-test comment had two, and
libxml2/ox happen to scan comments leniently (just looking for the next
literal "-->"), so it never surfaced there -- but nokogiri and rexml
correctly balk at it, and silently abandon the parse with no exception
partway through (WSDL::Parser#parse's do_parse call returns normally with
@lastnode still nil), which surfaced as a confusing NoMethodError two
layers up in WSDL::XMLSchema::Importer#import. Nothing to do with thread
interference or aggregate test-suite load, despite how it first looked --
confirmed by reproducing it in total isolation once nokogiri/rexml were
forced explicitly. Verified clean afterwards: full test:deep, all 5
parsers, 343 tests/0 failures/0 errors, on the official ruby:3.4.10 image
.github/workflows/ci.yml itself tests against.
…s found by it

httpclient's SSLConfig doesn't trust the system CA bundle by default -- it
lazily falls back to its own gem-vendored cacert.pem snapshot, which can go
stale relative to a real server's cert chain as CAs rotate intermediates
(confirmed against a real Let's Encrypt-signed endpoint). HTTPConfigLoader
now calls set_default_paths on every client's ssl_config by default, before
any user-supplied ssl_config options are applied on top.

Backend selection (lib/soap/streamHandler.rb) used to be a hardcoded
require/rescue cascade with no way to force a specific one, so
SOAP::NetHttpClient (the final fallback) was never reachable in practice --
this project's own Gemfile always installs httpclient. Refactored it into
lib/soap/httpbackend.rb + lib/soap/httpbackend/*.rb, mirroring
xsd/xmlparser.rb's SOAP4R_PARSERS env-override pattern, so backends are a
drop-in file and CI can sweep them the same way it sweeps XML parsers.

Making net_http actually reachable immediately surfaced two real bugs in it:
wiredump output duplicated request/response bodies (Net::HTTP's own raw
debug_output was enabled alongside this class's own structured dump to the
same stream) and was missing the raw request-line/header block entirely,
corrupting any wiredump_dev consumer. Fixed both, plus proxied requests now
recording the correct absolute-form request line. Several tests also assumed
"HTTPClient constant defined" meant "HTTPClient is the active backend" --
true before this change, no longer true now that other code (e.g.
test_ssl.rb) can load the gem independently of which backend is selected.
…nd Faraday adapter

Two new backends alongside httpclient/http-access2/net_http (see
lib/soap/httpbackend.rb): curb (libcurl bindings, real basic/digest
WWW-Authenticate support via libcurl's own challenge negotiation) and
faraday (itself pluggable underneath our own selection via
SOAP4R_FARADAY_ADAPTER, defaulting to :net_http). Both are opt-in Bundler
groups (:http_curb/:http_faraday, `optional: true`) rather than main-Gemfile
dependencies, since curb needs a system libcurl-dev present just to compile
and neither should force new build requirements onto contributors who never
touch them.

CI now sweeps httpclient/net_http/curb/faraday(:net_http) each on their own,
plus a deliberate spot-check of the Faraday bridge against a second,
meaningfully different adapter (:patron, also libcurl-based). Sweeping
Faraday's full adapter list wasn't worth doing: our own bridge code is what
needs testing, not Faraday's own adapter-selection correctness, which is
Faraday's test suite's job. That spot-check surfaced one narrow, accepted
exception -- patron's handling of WEBrick's CGI-subprocess responses -- now
documented in "Known Test Suite Exceptions" rather than silently ignored.

Also generalized test_streamhandler.rb's test_proxy (it was clearing
NO_PROXY_HOSTS on a hardcoded HTTPClient-or-NetHttpClient binary, which
missed the two new backend classes entirely) and test_basic.rb/test_digest.rb's
auth-support skip guards, so they dispatch on whichever backend is actually
active instead of a stale binary assumption.
… ours to fix)

typhoeus replaces patron as the real second Faraday spot-check adapter:
28 reverse dependencies on RubyGems versus patron's 3, so it actually
reflects what people configure Faraday with in practice, unlike patron
which was chosen purely for being libcurl-based.

Also dropped the dedicated Faraday+net_http CI step entirely -- it only
ever re-exercised stdlib Net::HTTP, which the native net_http backend
already covers end-to-end. The whole point of carrying Faraday is reaching
backends we have no native adapter for, and the bridge code itself is
exercised just as well by the typhoeus run (same lib/soap/faradayClient.rb
path, different adapter underneath).

patron was investigated rather than silently dropped: two CGI tests fail
under it with Patron::Aborted: Callback aborted, reproduced with a bare
Patron::Session#post against the exact same server with zero soap4r-ng or
Faraday code in the path at all, and not reproducible with curb (also
libcurl-based) or typhoeus. Root-caused as far as reasonably possible to
patron's own progress-callback timeout handling colliding with the CGI
handler's per-request subprocess spawn delay -- an upstream bug, not
something fixable from this side. The gem stays installed and usable
(SOAP4R_FARADAY_ADAPTER=patron) for ordinary requests; it's just excluded
from the automated matrix and documented as a known exception instead.
http-access2 was renamed to httpclient by its own author (Hiroaki "NaHi"
Nakamura) years ago and is no longer published on RubyGems.org at all --
confirmed empirically (`gem install http-access2` fails outright). Its
adapter in our own backend registry (lib/soap/httpbackend/http_access2.rb)
could therefore never actually load; it was pure dead code, always
LoadError'd past in the cascade.

Removed the adapter and its cascade entry, plus an independent, identical
dead http-access2 fallback in lib/wsdl/xmlSchema/importer.rb's own remote
WSDL-import HTTP client selection (unrelated to the backend registry, but
the same root problem). Updated comments/README referencing the old
three-way httpclient/http-access2/net_http cascade accordingly.

Confirmed forcing SOAP4R_HTTP_CLIENTS=http_access2 now fails cleanly with
"HTTP client backend not found" rather than silently falling through to
something else. All other backends (httpclient, curb, faraday+typhoeus,
net_http) still pass their full suites unmodified.
…llet

"(\`Logger::Application\` + \`include SOAP\` + \`include WEBrick\`)" happened
to wrap so that "+ \`include SOAP\`..." landed at the start of a physical
line, at valid list-continuation indentation -- and "+" is a legal
CommonMark bullet marker just like "*" and "-", so GitHub's renderer parsed
it as a new nested list item instead of the literal "plus" it was meant to
be. Reworded to "combined with ... and ..." so no physical line can ever
start with a bullet-marker character regardless of how the paragraph
rewraps later.
…L coverage for curb/Faraday

Broadened test coverage that was gated to httpclient only, closing the gap
between "backends exist" and "backends are actually tested the same way."

Wiredump-format tests (19 tests across test_rpc_lit.rb, test_qualified.rb,
test_unqualified.rb, test_no_indent.rb, test_anonymous.rb, and one in
test_aspdotnet.rb) turned out to already be backend-neutral by design --
NetHttpClient/CurbClient/FaradayClient were all built to mirror httpclient's
wiredump block layout exactly. Removing the httpclient-only guards and
running the suite confirmed this empirically (100% passed, unmodified,
under every backend); test_anonymous.rb's guard turned out to be
unnecessary entirely, since it never actually parses wiredump content.
Consolidated the previously-duplicated parsing logic into
TestUtil.parse_wiredump_request_body/parse_wiredump_response_body
(test/testutil.rb) as the one canonical, documented implementation.

test/soap/ssl/test_ssl.rb now runs its SSL config-loading coverage against
curb and Faraday too, not just httpclient: added test_ca_verification
(ca_file success/failure, backend-neutral) and rewrote test_ciphers to
expect each backend's real exception class -- empirically confirmed, not
guessed: OpenSSL::SSL::SSLError (httpclient), Curl::Err::CurlError (curb),
Faraday::Error (Faraday, any adapter -- SSLError and ConnectionFailed are
siblings, not one a subclass of the other, and different adapters raise
different ones for the same failure). test_options/test_verification/
test_property stay httpclient-only: they're built around
ssl_config.verify_callback, and neither libcurl-based backend exposes a
per-certificate Ruby callback hook at all -- a real architectural gap, not
negligence.

This surfaced and fixed real bugs in the process:
- CurbClient's ciphers= called a method that doesn't exist on Curl::Easy at
  all (confirmed empirically) -- libcurl's CURLOPT_SSL_CIPHER_LIST has no
  dedicated setter, only reachable via #setopt with the raw constant.
- FaradayClient passed client_cert/client_key as in-memory OpenSSL objects;
  confirmed the :typhoeus adapter rejects those and only accepts file
  paths, despite Faraday::SSLOptions documenting object support. Now
  round-tripped through a PEM tempfile, same as curb already did.
- Both CurbClient/FaradayClient's Tempfile cleanup used
  ObjectSpace.define_finalizer(self, proc { ... }) with the proc built in
  an instance-method context that closed over self -- a classic finalizer
  bug (Ruby warned "finalizer references object to be finalized") that
  meant the finalizer could never actually fire. Fixed by building the
  proc in a class method instead, so it only closes over the tempfile.

Also confirmed (and documented) a real, external gap: Faraday's own
faraday-typhoeus adapter never forwards Faraday::SSLOptions#ciphers to
ethon's ssl_cipher_list at all, so cipher-list restriction is silently
ignored under that adapter -- not something this bridge can fix.

Full regression: 5 XML parsers x httpclient (346 tests each), net_http
(341 -- correctly excludes test_ssl.rb entirely, since NetHttpClient has no
SSL config surface), curb (346), faraday+typhoeus (346), all 100% passed.
Gemfile and lib/soap/faradayClient.rb used Ruby 1.9+ hash-shorthand
syntax, breaking 1.8.7 entirely (Gemfile parse failure) or crashing
uncaught whenever SOAP4R_HTTP_CLIENTS=faraday was forced (SyntaxError
isn't a LoadError, so httpbackend.rb's rescue doesn't catch it).

test/soap/test_streamhandler.rb's test_proxy generalization (to dispatch
on whichever backend is active) called Module#const_defined? with the
2-arg (inherit) form, which is Ruby 1.9+ only.

lib/xsd/datatypes.rb's Time-to-DateTime conversion (only reached on
Rubies without Time#to_datetime, i.e. 1.8.7 in practice) hit the same
Rational-arithmetic DateTime bug already documented and worked around
a few lines below for string parsing -- applied the same fix (bake the
fractional seconds into DateTime.civil's constructor instead of adding
them afterward).

README.md documents a newly-observed collateral effect: two wsdl/document
tests intermittently receive a corrupted dateTime value when run as part
of the full suite (traced to lingering CGI/WEBrick subprocess state from
earlier tests, not a real DateTime bug -- confirmed clean in isolation).
Same root cause as the already-documented CGI/WEBrick environment
fragility, not a new independent issue.
…ial matrix re-run

Root-caused and fixed everything the earlier parallel run's intermittent
curb/faraday failures were actually pointing at -- none of it turned out to
be resource contention:

Gemfile gating was too coarse (a blanket >= 2.2 for the whole http_faraday
group, matching curb's own gate) for gems whose real floor has crept much
higher over time: faraday-patron's oldest ever release needs Ruby >= 2.4;
faraday-typhoeus's needs >= 2.6; ethon's own ffi dependency needs >= 2.6
paired with an explicit ~> 1.12.2 pin below Ruby 3.0 (current ffi releases
need >= 3.0). curb itself needed its gate raised to >= 2.4 too, for an
unrelated reason: its C extension references CURL_SSLVERSION_MAX_* constants
that don't exist before libcurl 7.54.0, and the official ruby:2.2.10/2.3.8
Docker Hub images ship an older libcurl-dev than that.

Two real code bugs, both only reachable once bundle install actually
succeeded with these groups enabled (which is exactly why the earlier
parallel run's noisier signal never isolated them):

- lib/soap/faradayClient.rb's build_connection unconditionally included
  :ciphers in the SSL options hash. Faraday::SSLOptions is a Struct, and
  Faraday merges this hash into it via []= (raises NameError for a
  non-member key, unlike a Hash) -- :ciphers was dropped from that struct
  for a stretch of faraday 2.x releases (absent on 2.8.1, restored on
  2.14.3), crashing every single request under the versions missing it.
  Now only included when the running faraday's SSLOptions actually has it.

- lib/soap/curbClient.rb and faradayClient.rb both called URI.parse(url)
  unconditionally in a few spots, but url can already be a URI object (see
  test_uri in test/soap/test_streamhandler.rb, and netHttpClient.rb's own
  existing url.is_a?(URI) guard for the same case) -- crashes on
  "bad URI(is not URI?)". curbClient.rb's build_curl also needed an
  explicit url.to_s before Curl::Easy.new: given a URI object there, curb's
  C extension doesn't coerce it and silently stores nil for the URL,
  surfacing much later and confusingly as "no implicit conversion of nil
  into String" deep inside curl/multi.rb's perform machinery.

README documents a new, confirmed-environmental (not a code bug) exception:
test_ca_verification/test_ciphers fail under curb on Ruby 2.4.10/2.5.9
specifically, traced to those Docker Hub images' older libcurl/OpenSSL not
validating a chain that newer libcurl handles fine -- httpclient/net_http
pass the identical test on the same Ruby versions.
…, and move investigation detail out of code comments into CHANGELOG.md

Ox pin corrections:
- Ruby 2.2.x-2.6.x resolve Ox 2.14.14 unconstrained (Ox's own gemspec
  added a hard Ruby >= 2.7 floor starting at 2.14.15, so anything older
  is stuck on 2.14.14). 2.14.14 segfaults inside Ox.sax_parse's
  :convert_special => true path on complex documents -- reproduced via a
  real crash in test_mapping.rb on 2.2.10/2.3.8/2.4.10/2.6.10. htmlentities
  avoids that code path entirely, so it's restored as a required
  dependency there, correcting an earlier change this branch made that
  dropped it for all of Ruby >= 2.2 without testing that specific range.
- Ruby 1.9.3-2.1.x pins Ox exactly to 2.14.6, the newest release before
  a later patch (2.14.7) introduces an unrelated rb_utf8_str_new crash on
  those same old Rubies, while still decoding named entities natively
  (htmlentities not needed there).
- Ruby 1.8.7 stays on the much older Ox 2.4.5 (2.14.6's extconf.rb fails
  to build there) with htmlentities restored, since 2.4.5 only decodes
  the basic 5 XML entities.
- Fixed an incidental CRLF bug found along the way: oxparser.rb's
  no-decoder branch used :skip_return, which discarded \r and broke
  CRLF round-tripping independent of entity decoding.

Gemfile version-gate branches (httpclient, ox, nokogiri, libxml-ruby,
test-unit) are now consistently ordered newest-Ruby-first, matching the
one block that already did this.

Faraday/curb gating tightened to their real floors (Faraday >= 2.6, an
enhancement for modern Rubies rather than a legacy capability carried
forward -- older Faraday releases don't match this project's adapter
assumptions or hit a genuine SSL bug on the two versions that get
further) and the corresponding CI steps gated to skip cleanly below
those floors instead of hard-failing the job. Fixed a latent CI bug
where `bundle config set with "..."` (Bundler 2.x-only syntax) silently
no-opped on the Bundler 1.17.3 every Ruby below 3.2 falls back to,
meaning curb/faraday were never actually exercised in CI on those
versions -- replaced with the BUNDLE_WITH env var, exported so it's
visible to bundle exec as well as bundle install.

Added CHANGELOG.md holding the full investigation detail (version-by-
version Ox testing, the Faraday architecture mismatch, the BUNDLE_WITH
bug, the port-clearing workaround, CI's Docker-per-version architecture,
the full known-test-exceptions writeup, and the Ox 2.14.14 segfault
correction) that had accumulated as long inline comments in Gemfile,
ci.yml, and README.md. Those three files now carry short pointers to
CHANGELOG.md instead, cutting roughly 700 lines of essay-style comments
down to one-line facts.

Verified with a full regression matrix (all 5 XML parsers where
applicable) across 1.8.7, 1.9.3, 2.0.0, 2.1.10, 2.2.10, 2.3.8, and
2.6.10: all green except 1.8.7, which shows only its pre-existing,
already-documented exceptions.
…failure

Ruby 2.4.10/2.5.9 have no continue-on-error anywhere in ci.yml, but their
Docker images' libcurl/OpenSSL pair fails test_ca_verification/
test_ciphers under curb -- a known, environment-specific CANTFIX already
documented in README's "Known Test Suite Exceptions", not a regression.
Scoped continue-on-error to just the curb step for these two versions
(matching the existing job-level pattern already used for 3.0.7/3.1.7/
3.2.11's own known exception) rather than the whole job, so every other
step for 2.4.10/2.5.9 -- the main parser sweep, net_http, faraday -- stays
fully enforced.
@rubyjedi
rubyjedi force-pushed the feature/curb-faraday-http-backends branch from 2da2255 to d19c27a Compare July 15, 2026 20:37
@rubyjedi rubyjedi closed this Jul 15, 2026
@rubyjedi

Copy link
Copy Markdown
Owner Author

This was a wild experiment w/ AI to see how well it can handle retrofitting patches -- it was not meant to go to github/origin, sorry.

@rubyjedi

Copy link
Copy Markdown
Owner Author

Superseded by #33 — same content, rebuilt as a single squashed commit on current master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants