Skip to content

Commit b1ad3f2

Browse files
authored
Rack 2 should not use to_ary. (#36)
1 parent 6a6829e commit b1ad3f2

File tree

4 files changed

+75
-16
lines changed

4 files changed

+75
-16
lines changed

lib/protocol/rack/adapter.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Released under the MIT License.
44
# Copyright, 2022-2026, by Samuel Williams.
55

6-
require "rack"
6+
require_relative "adapter/version"
77

88
module Protocol
99
module Rack
@@ -16,9 +16,6 @@ module Rack
1616
# response = adapter.call(request)
1717
# ```
1818
module Adapter
19-
# The version of Rack being used. Can be overridden using the PROTOCOL_RACK_ADAPTER_VERSION environment variable.
20-
VERSION = ENV.fetch("PROTOCOL_RACK_ADAPTER_VERSION", ::Rack.release)
21-
2219
if VERSION >= "3.1"
2320
require_relative "adapter/rack31"
2421
IMPLEMENTATION = Rack31
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2022-2026, by Samuel Williams.
5+
6+
require "rack"
7+
8+
module Protocol
9+
module Rack
10+
module Adapter
11+
# The version of Rack being used. Can be overridden using the PROTOCOL_RACK_ADAPTER_VERSION environment variable.
12+
VERSION = ENV.fetch("PROTOCOL_RACK_ADAPTER_VERSION", ::Rack.release)
13+
end
14+
end
15+
end

lib/protocol/rack/body/enumerable.rb

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
require "protocol/http/body/buffered"
88
require "protocol/http/body/file"
99

10+
require_relative "../adapter/version"
11+
1012
module Protocol
1113
module Rack
1214
module Body
@@ -17,18 +19,30 @@ class Enumerable < ::Protocol::HTTP::Body::Readable
1719
# The content-length header key.
1820
CONTENT_LENGTH = "content-length".freeze
1921

20-
# Wraps a Rack response body into an {Enumerable} instance.
21-
# If the body is an Array, its total size is calculated automatically.
22-
#
23-
# @parameter body [Object] The Rack response body that responds to `each`.
24-
# @parameter length [Integer] Optional content length of the response body.
25-
# @returns [Enumerable] A new enumerable body instance.
26-
def self.wrap(body, length = nil)
27-
if body.respond_to?(:to_ary)
28-
# This avoids allocating an enumerator, which is more efficient:
29-
return ::Protocol::HTTP::Body::Buffered.new(body.to_ary, length)
30-
else
31-
return self.new(body, length)
22+
if Adapter::VERSION >= "3"
23+
# Wraps a Rack response body into an {Enumerable} instance.
24+
# If the body is an Array, its total size is calculated automatically.
25+
#
26+
# @parameter body [Object] The Rack response body that responds to `each`.
27+
# @parameter length [Integer] Optional content length of the response body.
28+
# @returns [Enumerable] A new enumerable body instance.
29+
def self.wrap(body, length = nil)
30+
if body.respond_to?(:to_ary)
31+
# This avoids allocating an enumerator, which is more efficient:
32+
return ::Protocol::HTTP::Body::Buffered.new(body.to_ary, length)
33+
else
34+
return self.new(body, length)
35+
end
36+
end
37+
else
38+
def self.wrap(body, length = nil)
39+
# 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:
40+
if body.is_a?(Array)
41+
# This avoids allocating an enumerator, which is more efficient:
42+
return ::Protocol::HTTP::Body::Buffered.new(body, length)
43+
else
44+
return self.new(body, length)
45+
end
3246
end
3347
end
3448

test/protocol/rack/body/enumerable.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,37 @@
7777
end.to raise_exception(RuntimeError, message: be =~ /Bad Callable/)
7878
end
7979
end
80+
81+
with ".wrap with Rack::BodyProxy", if: defined?(Rack::BodyProxy) do
82+
it "calls close on the body proxy" do
83+
closed = false
84+
rack_body = Rack::BodyProxy.new(["Hello", "World"]) do
85+
closed = true
86+
end
87+
88+
wrapped = subject.wrap(rack_body)
89+
90+
# Consume the body
91+
chunks = []
92+
wrapped.each{|chunk| chunks << chunk}
93+
94+
# The close callback should have been called
95+
expect(closed).to be == true
96+
end
97+
98+
it "calls close even when reading the body" do
99+
closed = false
100+
rack_body = Rack::BodyProxy.new(["Hello", "World"]) do
101+
closed = true
102+
end
103+
104+
wrapped = subject.wrap(rack_body)
105+
106+
# Read all chunks
107+
wrapped.join
108+
109+
# The close callback should have been called
110+
expect(closed).to be == true
111+
end
112+
end
80113
end

0 commit comments

Comments
 (0)