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 1d720a5..d525f28 100644 --- a/test/protocol/rack/body/enumerable.rb +++ b/test/protocol/rack/body/enumerable.rb @@ -77,4 +77,37 @@ 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 + wrapped.join + + # The close callback should have been called + expect(closed).to be == true + end + end end