Skip to content

Commit 50a7734

Browse files
committed
Fix ChunkedBody double-encoding under Rack 3 / Rackup
Rack 2's Rack::Handler::WEBrick uses a monkey-patch on WEBrick::HTTPResponse#setup_header that sets @Chunked = true only during header setup (so Transfer-Encoding: chunked is emitted), then immediately restores @Chunked = false. This means WEBrick's send_body_string sends the response body as-is, relying on ChunkedBody to have pre-encoded it. Rackup 2.x's WEBrick handler takes the opposite approach: it buffers the entire body into a String, and WEBrick's []= setter permanently sets @Chunked = true when Transfer-Encoding: chunked is assigned, so WEBrick chunks the plain String itself. Pre-encoding with ChunkedBody under Rack 3 therefore produced double-chunked wire data that Net::HTTP decoded to raw chunk markers instead of the actual response body. Introduce a rack_v3? helper and guard the ChunkedBody wrapping behind it: under Rack 3 return the plain body (callable or enumerable) and let the server apply a single layer of chunked encoding; under Rack 2 keep the existing ChunkedBody behaviour.
1 parent a5a3e92 commit 50a7734

1 file changed

Lines changed: 24 additions & 4 deletions

File tree

lib/webmachine/adapters/rack.rb

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def run
5252
Host: application.configuration.ip
5353
}).merge(application.configuration.adapter_options)
5454

55-
if ::Rack.release.start_with?('3.')
55+
if rack_v3?
5656
require 'rackup'
5757
@server = ::Rackup::Server.new(options)
5858
else
@@ -83,10 +83,25 @@ def call(env)
8383
if (io_body = IO.try_convert(response.body))
8484
io_body
8585
elsif response.body.respond_to?(:call)
86-
Webmachine::ChunkedBody.new(Array(response.body.call))
86+
if rack_v3?
87+
# In Rack 3 the server (e.g. WEBrick via Rackup) buffers the body
88+
# and applies chunked encoding itself when Transfer-Encoding is set,
89+
# so we must not pre-encode with ChunkedBody.
90+
[response.body.call]
91+
else
92+
# Rack 2's WEBrick handler sends the body as-is; ChunkedBody is
93+
# required to produce valid chunked-encoded wire data.
94+
Webmachine::ChunkedBody.new(Array(response.body.call))
95+
end
8796
elsif response.body.respond_to?(:each)
88-
# This might be an IOEncoder with a Content-Length, which shouldn't be chunked.
89-
if response.headers[TRANSFER_ENCODING] == 'chunked'
97+
if rack_v3?
98+
# Return the plain enumerable. Rackup buffers it into a String then
99+
# WEBrick chunks that String when Transfer-Encoding: chunked is set.
100+
response.body
101+
elsif response.headers[TRANSFER_ENCODING] == 'chunked'
102+
# Rack 2: only pre-encode bodies that are already marked chunked;
103+
# IOEncoder bodies carry their own Content-Length and must not be
104+
# wrapped.
90105
Webmachine::ChunkedBody.new(response.body)
91106
else
92107
response.body
@@ -112,6 +127,11 @@ def base_uri(rack_req)
112127

113128
private
114129

130+
# Returns true when running under Rack 3.x.
131+
def rack_v3?
132+
::Rack.release.start_with?('3.')
133+
end
134+
115135
def build_webmachine_request(rack_req, headers)
116136
RackRequest.new(rack_req.request_method,
117137
rack_req.url,

0 commit comments

Comments
 (0)