diff --git a/lib/protocol/rack/body/enumerable.rb b/lib/protocol/rack/body/enumerable.rb index bce68c6..ff80dbb 100644 --- a/lib/protocol/rack/body/enumerable.rb +++ b/lib/protocol/rack/body/enumerable.rb @@ -4,6 +4,7 @@ # Copyright, 2022-2026, by Samuel Williams. require "protocol/http/body/readable" +require "protocol/http/body/buffered" require "protocol/http/body/file" module Protocol @@ -23,9 +24,9 @@ class Enumerable < ::Protocol::HTTP::Body::Readable # @parameter length [Integer] Optional content length of the response body. # @returns [Enumerable] A new enumerable body instance. def self.wrap(body, length = nil) - if body.is_a?(Array) - length ||= body.sum(&:bytesize) - return self.new(body, length) + if body.respond_to?(:to_ary) + # This avoids allocating an enumerator, which is more efficient: + return ::Protocol::HTTP::Body::Buffered.new(body.to_ary, length) else return self.new(body, length) end diff --git a/test/protocol/rack/adapter/rack2.rb b/test/protocol/rack/adapter/rack2.rb index f5e816e..3c904e9 100644 --- a/test/protocol/rack/adapter/rack2.rb +++ b/test/protocol/rack/adapter/rack2.rb @@ -59,7 +59,7 @@ let(:app) {->(env){[200, {}, body]}} it "should not modify partial responses" do - expect(response.body).to be_a(Protocol::Rack::Body::Enumerable) + expect(response.body).to be_a(Protocol::HTTP::Body::Buffered) end end end @@ -78,7 +78,7 @@ let(:app) {->(env){[200, {}, ["Hello"]]}} it "handles array response correctly" do - expect(response.body).to be_a(Protocol::Rack::Body::Enumerable) + expect(response.body).to be_a(Protocol::HTTP::Body::Buffered) end end end diff --git a/test/protocol/rack/adapter/rack3.rb b/test/protocol/rack/adapter/rack3.rb index ac5af56..61dbaca 100644 --- a/test/protocol/rack/adapter/rack3.rb +++ b/test/protocol/rack/adapter/rack3.rb @@ -57,7 +57,7 @@ let(:app) {->(env){[200, {}, fake_file]}} it "should not modify partial responses" do - expect(response.body).to be(:kind_of?, Protocol::Rack::Body::Enumerable) + expect(response.body).to be(:kind_of?, Protocol::HTTP::Body::Buffered) end end end diff --git a/test/protocol/rack/adapter/rack31.rb b/test/protocol/rack/adapter/rack31.rb index 5ca8240..3ee0d65 100644 --- a/test/protocol/rack/adapter/rack31.rb +++ b/test/protocol/rack/adapter/rack31.rb @@ -65,7 +65,7 @@ let(:app) {->(env){[200, {}, fake_file]}} it "should not modify partial responses" do - expect(response.body).to be(:kind_of?, Protocol::Rack::Body::Enumerable) + expect(response.body).to be(:kind_of?, Protocol::HTTP::Body::Buffered) end end end diff --git a/test/protocol/rack/body.rb b/test/protocol/rack/body.rb index 84d22d1..01d5a84 100644 --- a/test/protocol/rack/body.rb +++ b/test/protocol/rack/body.rb @@ -126,6 +126,41 @@ def body.each end end + with "array body" do + it "wraps array body and returns chunks via #read" do + result = subject.wrap(env, 200, headers, ["Hello", " ", "World"]) + + expect(result).not.to be_nil + expect(result.read).to be == "Hello" + expect(result.read).to be == " " + expect(result.read).to be == "World" + expect(result.read).to be_nil + end + + it "wraps array body and yields chunks via #each" do + result = subject.wrap(env, 200, headers, ["chunk1", "chunk2"]) + chunks = [] + result.each{|chunk| chunks << chunk} + + expect(chunks).to be == ["chunk1", "chunk2"] + end + + it "wraps empty array body" do + result = subject.wrap(env, 200, headers, []) + + expect(result).not.to be_nil + expect(result).to be(:empty?) + expect(result.read).to be_nil + end + + it "uses content-length when provided" do + headers["content-length"] = "11" + result = subject.wrap(env, 200, headers, ["Hello", " World"]) + + expect(result.length).to be == 11 + end + end + with "response finished callback" do it "returns a body that calls the callback when closed" do called = false diff --git a/test/protocol/rack/body/enumerable.rb b/test/protocol/rack/body/enumerable.rb index c74e9ac..1d720a5 100644 --- a/test/protocol/rack/body/enumerable.rb +++ b/test/protocol/rack/body/enumerable.rb @@ -22,6 +22,24 @@ end end + with "#read" do + let(:body) {subject.new(["Hello", " ", "World"], 11)} + + it "returns chunks in order" do + expect(body.read).to be == "Hello" + expect(body.read).to be == " " + expect(body.read).to be == "World" + expect(body.read).to be_nil + end + + it "returns nil when exhausted" do + body.read + body.read + body.read + expect(body.read).to be_nil + end + end + with "#each" do let(:bad_enumerable) do Enumerator.new do |yielder|