WebMockClientWrapper#build_request_signature calls request.headers.to_h (line 103 in async_http_client_adapter.rb). Starting with protocol-http 0.59.0, Headers#to_h invokes Header::Split.parse(value) on each header entry during merging. When a header has a nil value (which is valid in the Protocol::HTTP::Headers collection for internal/connection headers), Split.parse calls value.split(COMMA) on nil and raises:
NoMethodError: undefined method 'split' for nil
protocol-http-0.59.0/lib/protocol/http/header/split.rb:23:in 'Protocol::HTTP::Header::Split.parse'
protocol-http-0.59.0/lib/protocol/http/headers.rb:452:in 'Protocol::HTTP::Headers#merge_into'
protocol-http-0.59.0/lib/protocol/http/headers.rb:473:in 'block in Protocol::HTTP::Headers#to_h'
webmock-3.26.1/lib/webmock/http_lib_adapters/async_http_client_adapter.rb:103:in 'build_request_signature'
Steps to reproduce:
# Gemfile
gem "faraday"
gem "async-http-faraday"
gem "webmock"
# test
require "async/http/faraday/default" # sets async-http as Faraday default adapter
require "webmock/rspec"
WebMock.disable_net_connect!
stub_request(:get, "https://example.com/test")
.to_return(status: 200, body: '{"ok":true}')
# This crashes:
Faraday.get("https://example.com/test")
# => NoMethodError: undefined method 'split' for nil
Environment
webmock 3.26.1
protocol-http >= 0.59.0
async-http >= 0.94.0
async-http-faraday 0.22.1
Ruby 3.2+ / 3.4+
Root Cause
In async_http_client_adapter.rb line 103:
def build_request_signature(request)
body = request.read
request.body = ::Protocol::HTTP::Body::Buffered.wrap(body)
WebMock::RequestSignature.new(
request.method.downcase.to_sym,
"#{request.scheme}://#{request.authority}#{request.path}",
headers: request.headers.to_h, # <--- crashes here
body: body
)
end
request.headers can contain entries with nil values. The protocol-http 0.59.0 Headers#to_h now strictly parses every value through header policy classes, which don't handle nil.
Suggested Fix
Filter out nil-valued headers before conversion:
def build_request_signature(request)
body = request.read
request.body = ::Protocol::HTTP::Body::Buffered.wrap(body)
headers = {}
request.headers.each do |key, value|
headers[key] = value unless value.nil?
end
WebMock::RequestSignature.new(
request.method.downcase.to_sym,
"#{request.scheme}://#{request.authority}#{request.path}",
headers: headers,
body: body
)
end
Workaround
if defined?(Async::HTTP::WebMockClientWrapper)
module Async
module HTTP
class WebMockClientWrapper
private
def build_request_signature(request)
body = request.read
request.body = ::Protocol::HTTP::Body::Buffered.wrap(body)
headers = {}
request.headers.each do |key, value|
headers[key] = value unless value.nil?
end
WebMock::RequestSignature.new(
request.method.downcase.to_sym,
"#{request.scheme}://#{request.authority}#{request.path}",
headers: headers,
body: body
)
end
end
end
end
end
WebMockClientWrapper#build_request_signature calls request.headers.to_h (line 103 in async_http_client_adapter.rb). Starting with protocol-http 0.59.0, Headers#to_h invokes Header::Split.parse(value) on each header entry during merging. When a header has a nil value (which is valid in the Protocol::HTTP::Headers collection for internal/connection headers), Split.parse calls value.split(COMMA) on nil and raises:
Steps to reproduce:
Environment
webmock 3.26.1
protocol-http >= 0.59.0
async-http >= 0.94.0
async-http-faraday 0.22.1
Ruby 3.2+ / 3.4+
Root Cause
In async_http_client_adapter.rb line 103:
request.headers can contain entries with nil values. The protocol-http 0.59.0 Headers#to_h now strictly parses every value through header policy classes, which don't handle nil.
Suggested Fix
Filter out nil-valued headers before conversion:
Workaround