Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions lib/protocol/rack/adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
15 changes: 15 additions & 0 deletions lib/protocol/rack/adapter/version.rb
Original file line number Diff line number Diff line change
@@ -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
38 changes: 26 additions & 12 deletions lib/protocol/rack/body/enumerable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
require "protocol/http/body/buffered"
require "protocol/http/body/file"

require_relative "../adapter/version"

module Protocol
module Rack
module Body
Expand All @@ -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

Expand Down
33 changes: 33 additions & 0 deletions test/protocol/rack/body/enumerable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading