From 9610faa8112ada9c001c2b2d015dce2b883c7018 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 31 Mar 2026 12:02:51 +1300 Subject: [PATCH 1/3] Failing tests. --- test/protocol/rack/body/enumerable.rb | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/protocol/rack/body/enumerable.rb b/test/protocol/rack/body/enumerable.rb index 1d720a5..7c32419 100644 --- a/test/protocol/rack/body/enumerable.rb +++ b/test/protocol/rack/body/enumerable.rb @@ -5,6 +5,12 @@ require "protocol/rack/body/enumerable" +begin + require "rack" +rescue LoadError + # Rack not available +end + describe Protocol::Rack::Body::Enumerable do with "empty body" do let(:body) {subject.new([], nil)} @@ -77,4 +83,40 @@ end.to raise_exception(RuntimeError, message: be =~ /Bad Callable/) end end + + with ".wrap with Rack::BodyProxy", if: defined?(Rack::BodyProxy) do + it "calls close on the body proxy" do + closed = false + rack_body = Rack::BodyProxy.new(["Hello", "World"]) do + closed = true + end + + wrapped = subject.wrap(rack_body) + + # Consume the body + chunks = [] + wrapped.each { |chunk| chunks << chunk } + + # The close callback should have been called + expect(closed).to be == true + end + + it "calls close even when reading the body" do + closed = false + rack_body = Rack::BodyProxy.new(["Hello", "World"]) do + closed = true + end + + wrapped = subject.wrap(rack_body) + + # Read all chunks + chunks = [] + while chunk = wrapped.read + chunks << chunk + end + + # The close callback should have been called + expect(closed).to be == true + end + end end From 3a6f40476d8a0c99622836a54b20870af8cfc52a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 31 Mar 2026 12:22:30 +1300 Subject: [PATCH 2/3] Rack 2 should not use `to_ary`. Fixes #35. --- lib/protocol/rack/adapter.rb | 5 +--- lib/protocol/rack/adapter/version.rb | 15 +++++++++++ lib/protocol/rack/body/enumerable.rb | 38 ++++++++++++++++++--------- test/protocol/rack/body/enumerable.rb | 11 +------- 4 files changed, 43 insertions(+), 26 deletions(-) create mode 100644 lib/protocol/rack/adapter/version.rb diff --git a/lib/protocol/rack/adapter.rb b/lib/protocol/rack/adapter.rb index cc89c49..107b4a1 100644 --- a/lib/protocol/rack/adapter.rb +++ b/lib/protocol/rack/adapter.rb @@ -3,7 +3,7 @@ # Released under the MIT License. # Copyright, 2022-2026, by Samuel Williams. -require "rack" +require_relative "adapter/version" module Protocol module Rack @@ -16,9 +16,6 @@ module Rack # response = adapter.call(request) # ``` module Adapter - # The version of Rack being used. Can be overridden using the PROTOCOL_RACK_ADAPTER_VERSION environment variable. - VERSION = ENV.fetch("PROTOCOL_RACK_ADAPTER_VERSION", ::Rack.release) - if VERSION >= "3.1" require_relative "adapter/rack31" IMPLEMENTATION = Rack31 diff --git a/lib/protocol/rack/adapter/version.rb b/lib/protocol/rack/adapter/version.rb new file mode 100644 index 0000000..a09c60b --- /dev/null +++ b/lib/protocol/rack/adapter/version.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2022-2026, by Samuel Williams. + +require "rack" + +module Protocol + module Rack + module Adapter + # The version of Rack being used. Can be overridden using the PROTOCOL_RACK_ADAPTER_VERSION environment variable. + VERSION = ENV.fetch("PROTOCOL_RACK_ADAPTER_VERSION", ::Rack.release) + end + end +end diff --git a/lib/protocol/rack/body/enumerable.rb b/lib/protocol/rack/body/enumerable.rb index ff80dbb..eaf43cb 100644 --- a/lib/protocol/rack/body/enumerable.rb +++ b/lib/protocol/rack/body/enumerable.rb @@ -7,6 +7,8 @@ require "protocol/http/body/buffered" require "protocol/http/body/file" +require_relative "../adapter/version" + module Protocol module Rack module Body @@ -17,18 +19,30 @@ class Enumerable < ::Protocol::HTTP::Body::Readable # The content-length header key. CONTENT_LENGTH = "content-length".freeze - # Wraps a Rack response body into an {Enumerable} instance. - # If the body is an Array, its total size is calculated automatically. - # - # @parameter body [Object] The Rack response body that responds to `each`. - # @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.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) + if Adapter::VERSION >= "3" + # Wraps a Rack response body into an {Enumerable} instance. + # If the body is an Array, its total size is calculated automatically. + # + # @parameter body [Object] The Rack response body that responds to `each`. + # @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.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 + end + else + def self.wrap(body, length = nil) + # Rack 2 does not specify or implement `to_ary` behaviour correctly, so the best we can do is check if it's an Array directly: + if body.is_a?(Array) + # This avoids allocating an enumerator, which is more efficient: + return ::Protocol::HTTP::Body::Buffered.new(body, length) + else + return self.new(body, length) + end end end diff --git a/test/protocol/rack/body/enumerable.rb b/test/protocol/rack/body/enumerable.rb index 7c32419..f46c8a7 100644 --- a/test/protocol/rack/body/enumerable.rb +++ b/test/protocol/rack/body/enumerable.rb @@ -5,12 +5,6 @@ require "protocol/rack/body/enumerable" -begin - require "rack" -rescue LoadError - # Rack not available -end - describe Protocol::Rack::Body::Enumerable do with "empty body" do let(:body) {subject.new([], nil)} @@ -110,10 +104,7 @@ wrapped = subject.wrap(rack_body) # Read all chunks - chunks = [] - while chunk = wrapped.read - chunks << chunk - end + wrapped.join # The close callback should have been called expect(closed).to be == true From 58e0ec4952338743fbfc22b4a2db7725529eb2b2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 31 Mar 2026 12:23:53 +1300 Subject: [PATCH 3/3] RuboCop. --- test/protocol/rack/body/enumerable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/protocol/rack/body/enumerable.rb b/test/protocol/rack/body/enumerable.rb index f46c8a7..d525f28 100644 --- a/test/protocol/rack/body/enumerable.rb +++ b/test/protocol/rack/body/enumerable.rb @@ -89,7 +89,7 @@ # Consume the body chunks = [] - wrapped.each { |chunk| chunks << chunk } + wrapped.each{|chunk| chunks << chunk} # The close callback should have been called expect(closed).to be == true