Skip to content

Commit 650f7ea

Browse files
committed
test: add real-socket test for response body limit
Adds a failing test that exercises the OTLP exporter's response body limit against a real TCPServer instead of WebMock stubs. WebMock patches Net::HTTP's read_body internals, so the existing stub-based tests pass even though the chunked reader doesn't work against real HTTP. With WebMock's adapter fully disabled, we see that Net::HTTP#request eagerly reads the full body before read_response_body runs, causing IOError: "read_body called twice". The IOError is the symptom — the actual problem is that the full oversized response body is already in memory by that point, defeating the 4 MB cap. Reproduces the problem described in review comments on PR open-telemetry#2080.
1 parent f0bec26 commit 650f7ea

1 file changed

Lines changed: 64 additions & 0 deletions

File tree

exporter/otlp/test/opentelemetry/exporter/otlp/exporter_test.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,70 @@ def create_link(span_context)
10421042
end
10431043
end
10441044

1045+
describe 'response body reading with real sockets' do
1046+
# These tests use a real TCPServer instead of WebMock to verify
1047+
# behavior against actual Net::HTTP socket I/O. WebMock's patching
1048+
# of Net::HTTP changes how read_body works — even with
1049+
# allow_net_connect!, responses go through WebMock's adapter which
1050+
# doesn't exercise the same code paths as real Net::HTTP.
1051+
1052+
def with_fake_server(response_body_size:, status: 400)
1053+
server = TCPServer.new('127.0.0.1', 0)
1054+
port = server.addr[1]
1055+
body = 'X' * response_body_size
1056+
1057+
server_thread = Thread.new do
1058+
client = server.accept
1059+
content_length = 0
1060+
while (line = client.gets) && line != "\r\n"
1061+
content_length = line.split(': ', 2).last.to_i if line.start_with?('Content-Length')
1062+
end
1063+
client.read(content_length) if content_length > 0
1064+
1065+
client.print "HTTP/1.1 #{status} Bad Request\r\n"
1066+
client.print "Content-Type: application/octet-stream\r\n"
1067+
client.print "Content-Length: #{body.bytesize}\r\n"
1068+
client.print "Connection: close\r\n"
1069+
client.print "\r\n"
1070+
client.write body
1071+
client.close
1072+
rescue => e
1073+
# client may disconnect early
1074+
end
1075+
1076+
# Fully disable WebMock's Net::HTTP adapter so we get real
1077+
# socket behavior, not WebMock's patched read_body.
1078+
WebMock::HttpLibAdapters::NetHttpAdapter.disable!
1079+
yield port
1080+
ensure
1081+
WebMock::HttpLibAdapters::NetHttpAdapter.enable!
1082+
server&.close
1083+
server_thread&.join(2)
1084+
end
1085+
1086+
it 'limits error response body read to 4 MB against a real HTTP server' do
1087+
log_stream = StringIO.new
1088+
logger = OpenTelemetry.logger
1089+
OpenTelemetry.logger = ::Logger.new(log_stream)
1090+
1091+
with_fake_server(response_body_size: 5_000_000) do |port|
1092+
exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(
1093+
endpoint: "http://127.0.0.1:#{port}/v1/traces"
1094+
)
1095+
span_data = OpenTelemetry::TestHelpers.create_span_data
1096+
result = exporter.export([span_data])
1097+
1098+
_(result).must_equal(FAILURE)
1099+
# The body cap should work without hitting "read_body called twice".
1100+
# If we see this error, it means read_response_body was called after
1101+
# Net::HTTP already read the full body during @http.request().
1102+
_(log_stream.string).wont_match(/read_body called twice/)
1103+
end
1104+
ensure
1105+
OpenTelemetry.logger = logger
1106+
end
1107+
end
1108+
10451109
describe 'response body reading' do
10461110
let(:exporter) { OpenTelemetry::Exporter::OTLP::Exporter.new }
10471111
let(:span_data) { OpenTelemetry::TestHelpers.create_span_data }

0 commit comments

Comments
 (0)