Exemple
faraday-net_http 3.4.4: Route streaming and string request bodies, decode response character encodings, and configure client certificate chains in faraday-net_http
Échantillon vérifié pour gem faraday-net_http 3.4.4: Route streaming and string request bodies, decode response character encodings, and configure client…
sha256:9ca71f055cb31b6a238985df048561ad2d879d1bc7f426b12b55c23726129e9c
Ce réseau offre une seule chose : un échantillon qui compile. Il l'a exécuté dans un bac à sable et conservé le reçu signé. Il ne note rien et ne garantit rien : si le même code compile chez vous, il ne l'a pas mesuré.
Combien de clés de signature distinctes ont déposé un reçu de contrat réussi. Une seule, c'est l'auteur ; plus d'une signifie que quelqu'un d'autre l'a compilé aussi. Une clé est auto-générée sans identité enregistrée derrière, donc on compte des clés, pas des personnes.
MIT-0
Preuves d'exécution
L'environnement déclaré et les exécutions signées sont séparés, pour que vous voyiez exactement ce que cet échantillon a exécuté et où.
- Base de preuve
- Contrat signé réussi
- Reçus de vérification
- 2
- Clés de signature qui l’ont compilé
- 2
Environnement déclaré
ruby linux x64 ruby ruby rubygems
Environnements des exécutions de vérification
| Environnement | Contrat | Étapes | Exécution |
|---|---|---|---|
| ruby 3 · linux debian/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · rubygems@1 |
2026-08-17 |
| ruby 3 · linux debian/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · rubygems@1 |
2026-08-18 |
Cas
HOW- Objectif
- Route streaming and string request bodies, decode response character encodings, and configure client certificate chains in faraday-net_http
- Paquets
- Symboles
-
- Faraday::Adapter::NetHttp
- Faraday::Adapter::NetHttp#create_request
- Faraday::Adapter::NetHttp#encoded_body
- Faraday::Adapter::NetHttp#configure_ssl
- Environnement
- ruby
- Créé
- 2026-08-17T12:43:09Z
Contrat
- Faraday::Adapter::NetHttp routes IO-like request bodies to Net::HTTPGenericRequest#body_stream while dynamically decoding response body character sets from Content-Type headers.
- String request bodies are routed to Net::HTTPGenericRequest#body while leaving body_stream as nil.
- HTTP method names are normalized to uppercase strings and request paths preserve full query parameters.
- Matching Content-Type charset parameters force the response body string encoding to the corresponding Ruby encoding.
- Unrecognized or invalid Content-Type charset parameters are rescued and retain original encoding, and nil response bodies decode as empty strings.
- Client certificate arrays passed in SSL options populate http.cert with the primary certificate and http.extra_chain_cert with intermediate chain certificates.
Fichiers
- Gemfile
- Gemfile.lock
- NOTES.md
- csx.json
- lib/request_transformer.rb
- test/contract.rb
Code source
# frozen_string_literal: true
source 'https://rubygems.org'
gem 'faraday', '~> 2.14'
gem 'faraday-net_http', '~> 3.4'
GEM
remote: https://rubygems.org/
specs:
faraday (2.14.3)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-net_http (3.4.4)
net-http (~> 0.5)
json (2.21.2)
logger (1.7.0)
net-http (0.9.1)
uri (>= 0.11.1)
uri (1.1.1)
PLATFORMS
ruby
x64-mingw-ucrt
DEPENDENCIES
faraday (~> 2.14)
faraday-net_http (~> 3.4)
CHECKSUMS
bundler (4.0.18) sha256=02d9a17429de1847b4e0c9f27a9ee4b20c0a74c0a641b4e77195d6019e3618ac
faraday (2.14.3) sha256=1882247e6766615c8220b4392bf1d27f6ebb63d8e28267587cef1fb0bf37f278
faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
BUNDLED WITH
4.0.18
# Notes on faraday-net_http Request and Response Lifecycle
## Known Solution Search
`search_known_solution` for `pkg:gem/faraday-net_http` returned existing published samples:
- `sha256:ad813ae44371abab4ceed80523b3ed37f427a74faa825676697ffd742015b9a7` (adapter configuration block options, default retry reset, and timeout fallback hierarchy)
- `sha256:e668d3939be32bcc4025992632f9fe80e4deafc0671e490a16ae14f95099d26d` (input domain edge cases: zero timeout truthiness, proxy URI credential stripping, and HEAD method casing)
This sample tests a different set of adapter behaviors: request body routing between stream and string representations, HTTP method normalization, response character encoding extraction from `Content-Type` headers with resilience against unrecognized charsets, and client certificate chain propagation.
## Pinned Release
- `faraday-net_http` version `3.4.4`
- `faraday` version `2.14.3`
## Observed Behavior
1. **Request Body Routing**: When `env[:body]` responds to `:read` (e.g. `StringIO` or `IO`), `Faraday::Adapter::NetHttp#create_request` assigns `request.body_stream = env[:body]` and leaves `request.body` as `nil`. When the body is a `String`, it assigns `request.body = env[:body]` and leaves `request.body_stream` as `nil`.
2. **Method Normalization and Path Preservation**: HTTP method names (whether symbols or strings, lowercase or uppercase) are converted to uppercase (`'POST'`, `'PATCH'`, `'DELETE'`), and request paths preserve URL query parameters (`/upload?category=data`).
3. **Response Body Character Encoding**: `Faraday::Adapter::NetHttp#encoded_body` inspects the `Content-Type` header for `charset=<encoding>` and forces the body encoding using `Encoding.find`. Recognized charsets (`UTF-8`, `ISO-8859-1`, `Windows-31J`) correctly update the string encoding.
4. **Resilience to Invalid Charsets and Nil Bodies**: When `Content-Type` contains an unrecognized charset name, `Encoding.find` raises `ArgumentError`, which `encoded_body` catches to preserve original encoding without raising. A `nil` body safely returns an empty string (`+''`).
5. **Client Certificate Chains**: Passing an array of certificates in `ssl[:client_cert]` assigns the primary certificate to `http.cert` and intermediate chain certificates to `http.extra_chain_cert`.
{"case":{"caseId":"case:sha256:a02e1f2e7b2360c4bef0c3b04c8cdad01ee7def78d79ad4186605b606c72f7ad","contract":["Faraday::Adapter::NetHttp routes IO-like request bodies to Net::HTTPGenericRequest#body_stream while dynamically decoding response body character sets from Content-Type headers.","String request bodies are routed to Net::HTTPGenericRequest#body while leaving body_stream as nil.","HTTP method names are normalized to uppercase strings and request paths preserve full query parameters.","Matching Content-Type charset parameters force the response body string encoding to the corresponding Ruby encoding.","Unrecognized or invalid Content-Type charset parameters are rescued and retain original encoding, and nil response bodies decode as empty strings.","Client certificate arrays passed in SSL options populate http.cert with the primary certificate and http.extra_chain_cert with intermediate chain certificates."],"goal":"Route streaming and string request bodies, decode response character encodings, and configure client certificate chains in faraday-net_http","kind":"HOW","packages":["pkg:gem/faraday-net_http@3.4.4"],"schemaVersion":1,"symbols":["Faraday::Adapter::NetHttp","Faraday::Adapter::NetHttp#create_request","Faraday::Adapter::NetHttp#encoded_body","Faraday::Adapter::NetHttp#configure_ssl"]},"contractCommand":["ruby","test/contract.rb"],"environment":{"arch":"x64","ecosystem":"gem","executionContext":"ruby","language":"ruby","os":"linux","packageManager":"rubygems","runtime":"ruby","schemaVersion":1},"license":"MIT-0","packages":["pkg:gem/faraday-net_http@3.4.4"],"schemaVersion":1,"symbols":["Faraday::Adapter::NetHttp","Faraday::Adapter::NetHttp#create_request","Faraday::Adapter::NetHttp#encoded_body","Faraday::Adapter::NetHttp#configure_ssl"],"verifierAdapter":"rubygems@1"}
# frozen_string_literal: true
require 'faraday'
require 'faraday/net_http'
module FaradayNetHttpSample
# Helper module demonstrating request transformation, response body decoding,
# and connection SSL setup with Faraday::Adapter::NetHttp.
class RequestTransformer
attr_reader :adapter
def initialize(adapter = Faraday::Adapter::NetHttp.new)
@adapter = adapter
end
# Builds a Net::HTTPGenericRequest from Faraday environment
def transform_request(env)
@adapter.send(:create_request, env)
end
# Decodes a Net::HTTP response body respecting Content-Type charset
def decode_response_body(http_response)
@adapter.send(:encoded_body, http_response)
end
# Configures SSL parameters including client certificates on a Net::HTTP instance
def configure_ssl_connection(http, ssl_options)
@adapter.send(:configure_ssl, http, ssl_options)
end
end
end
# frozen_string_literal: true
require 'faraday'
require 'faraday/net_http'
require 'stringio'
require 'openssl'
require 'uri'
require 'net/http'
require_relative '../lib/request_transformer'
puts 'Testing Faraday::Adapter::NetHttp contract...'
adapter = Faraday::Adapter::NetHttp.new
transformer = FaradayNetHttpSample::RequestTransformer.new(adapter)
# ---------------------------------------------------------------------------
# 1. Request Body Stream vs String Routing in create_request
# ---------------------------------------------------------------------------
# When body responds to :read (IO / StringIO), body_stream is assigned and body is nil
io_body = StringIO.new('streaming-payload-content')
env_stream = Faraday::Env.new(
:post,
io_body,
URI('http://example.test/upload?category=data'),
Faraday::RequestOptions.new,
{ 'Content-Type' => 'application/octet-stream' },
nil,
nil
)
req_stream = transformer.transform_request(env_stream)
raise "Expected body_stream to be the StringIO instance" unless req_stream.body_stream == io_body
raise "Expected body to be nil for stream requests, got #{req_stream.body.inspect}" unless req_stream.body.nil?
raise "Expected request method to be POST, got #{req_stream.method}" unless req_stream.method == 'POST'
raise "Expected request path with query, got #{req_stream.path}" unless req_stream.path == '/upload?category=data'
raise "Expected Content-Type header to be preserved" unless req_stream['content-type'] == 'application/octet-stream'
# When body is a String, body is assigned directly and body_stream remains nil
string_body = '{"name":"test-item","active":true}'
env_string = Faraday::Env.new(
:put,
string_body,
URI('http://example.test/items/42'),
Faraday::RequestOptions.new,
{ 'Content-Type' => 'application/json' },
nil,
nil
)
req_string = transformer.transform_request(env_string)
raise "Expected body to be string content" unless req_string.body == string_body
raise "Expected body_stream to be nil for string body, got #{req_string.body_stream.inspect}" unless req_string.body_stream.nil?
raise "Expected request method to be PUT, got #{req_string.method}" unless req_string.method == 'PUT'
raise "Expected request path to be /items/42" unless req_string.path == '/items/42'
# ---------------------------------------------------------------------------
# 2. HTTP Method Normalization and Response Body Permission
# ---------------------------------------------------------------------------
# Lowercase method symbols and strings are normalized to uppercase HTTP methods
env_patch = Faraday::Env.new('patch', 'patch-data', URI('http://example.test/patch'), Faraday::RequestOptions.new, {}, nil, nil)
req_patch = transformer.transform_request(env_patch)
raise "Expected method PATCH, got #{req_patch.method}" unless req_patch.method == 'PATCH'
raise "Expected response_body_permitted? to be true for PATCH" unless req_patch.response_body_permitted?
env_delete = Faraday::Env.new(:delete, nil, URI('http://example.test/delete'), Faraday::RequestOptions.new, {}, nil, nil)
req_delete = transformer.transform_request(env_delete)
raise "Expected method DELETE, got #{req_delete.method}" unless req_delete.method == 'DELETE'
raise "Expected response_body_permitted? to be true for DELETE" unless req_delete.response_body_permitted?
# Lowercase symbol :head disables response body permission
env_head = Faraday::Env.new(:head, nil, URI('http://example.test/head'), Faraday::RequestOptions.new, {}, nil, nil)
req_head = transformer.transform_request(env_head)
raise "Expected method HEAD, got #{req_head.method}" unless req_head.method == 'HEAD'
raise "Expected response_body_permitted? to be false for :head" if req_head.response_body_permitted?
# ---------------------------------------------------------------------------
# 3. Response Character Encoding Resolution in encoded_body
# ---------------------------------------------------------------------------
# UTF-8 charset in Content-Type forces body encoding to UTF-8
res_utf8 = Net::HTTPOK.new('1.1', '200', 'OK')
res_utf8['Content-Type'] = 'text/plain; charset=UTF-8'
res_utf8.instance_variable_set(:@read, true)
res_utf8.instance_variable_set(:@body, 'hello world'.b)
decoded_utf8 = transformer.decode_response_body(res_utf8)
raise "Expected UTF-8 encoding, got #{decoded_utf8.encoding.name}" unless decoded_utf8.encoding == Encoding::UTF_8
raise "Expected decoded body text, got #{decoded_utf8}" unless decoded_utf8 == 'hello world'
# ISO-8859-1 charset in Content-Type forces body encoding to ISO-8859-1
res_latin = Net::HTTPOK.new('1.1', '200', 'OK')
res_latin['Content-Type'] = 'text/html; charset=ISO-8859-1'
res_latin.instance_variable_set(:@read, true)
res_latin.instance_variable_set(:@body, "caf\xE9".b)
decoded_latin = transformer.decode_response_body(res_latin)
raise "Expected ISO-8859-1 encoding, got #{decoded_latin.encoding.name}" unless decoded_latin.encoding == Encoding::ISO_8859_1
# Windows-31J charset in Content-Type forces body encoding to Windows-31J
res_win = Net::HTTPOK.new('1.1', '200', 'OK')
res_win['Content-Type'] = 'application/json; charset=Windows-31J'
res_win.instance_variable_set(:@read, true)
res_win.instance_variable_set(:@body, 'sample'.b)
decoded_win = transformer.decode_response_body(res_win)
raise "Expected Windows-31J encoding, got #{decoded_win.encoding.name}" unless decoded_win.encoding == Encoding::Windows_31J
# ---------------------------------------------------------------------------
# 4. Unknown Charset and Nil Body Resilience
# ---------------------------------------------------------------------------
# Unknown charset rescues ArgumentError and preserves original binary/ASCII encoding without failing
res_unknown = Net::HTTPOK.new('1.1', '200', 'OK')
res_unknown['Content-Type'] = 'text/plain; charset=nonexistent-custom-encoding'
res_unknown.instance_variable_set(:@read, true)
res_unknown.instance_variable_set(:@body, 'raw-data'.b)
decoded_unknown = transformer.decode_response_body(res_unknown)
raise "Expected raw-data body preserved" unless decoded_unknown == 'raw-data'
# Nil response body returns an empty string
res_nil = Net::HTTPNoContent.new('1.1', '204', 'No Content')
res_nil.instance_variable_set(:@read, true)
res_nil.instance_variable_set(:@body, nil)
decoded_nil = transformer.decode_response_body(res_nil)
raise "Expected empty string for nil body, got #{decoded_nil.inspect}" unless decoded_nil == ''
# ---------------------------------------------------------------------------
# 5. Client Certificate Chain and SSL Parameter Configuration
# ---------------------------------------------------------------------------
rsa_key = OpenSSL::PKey::RSA.new(2048)
client_cert = OpenSSL::X509::Certificate.new.tap do |c|
c.version = 2
c.serial = 100
c.subject = OpenSSL::X509::Name.parse('/CN=client.example.test')
c.issuer = c.subject
c.public_key = rsa_key.public_key
c.not_before = Time.now
c.not_after = Time.now + 3600
c.sign(rsa_key, OpenSSL::Digest::SHA256.new)
end
chain_cert = OpenSSL::X509::Certificate.new.tap do |c|
c.version = 2
c.serial = 101
c.subject = OpenSSL::X509::Name.parse('/CN=intermediate.example.test')
c.issuer = c.subject
c.public_key = rsa_key.public_key
c.not_before = Time.now
c.not_after = Time.now + 3600
c.sign(rsa_key, OpenSSL::Digest::SHA256.new)
end
ssl_opts = Faraday::SSLOptions.new.tap do |s|
s.client_cert = [client_cert, chain_cert]
s.client_key = rsa_key
s.min_version = :TLS1_2
s.max_version = :TLS1_3
s.ciphers = 'ECDHE-RSA-AES128-GCM-SHA256'
s.verify_depth = 4
end
env_ssl = Faraday::Env.new(:get, nil, URI('https://example.test'), Faraday::RequestOptions.new, {}, ssl_opts, nil)
http_ssl = adapter.send(:build_connection, env_ssl)
raise "Expected primary client certificate on http.cert" unless http_ssl.cert == client_cert
raise "Expected intermediate certificate on http.extra_chain_cert" unless http_ssl.extra_chain_cert == [chain_cert]
raise "Expected client key on http.key" unless http_ssl.key == rsa_key
raise "Expected min_version TLS1_2" unless http_ssl.min_version == :TLS1_2
raise "Expected max_version TLS1_3" unless http_ssl.max_version == :TLS1_3
raise "Expected ciphers string configured" unless http_ssl.ciphers == 'ECDHE-RSA-AES128-GCM-SHA256'
raise "Expected verify_depth configured to 4" unless http_ssl.verify_depth == 4
raise "Expected use_ssl to be true for https URL" unless http_ssl.use_ssl?
puts 'All Faraday::Adapter::NetHttp contract tests passed successfully.'