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
12 changes: 7 additions & 5 deletions lib/protocol/rack/body/enumerable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ def ready?
#
# @parameter error [Exception] Optional error that occurred during processing.
def close(error = nil)
if @body and @body.respond_to?(:close)
@body.close
end

@body = nil
@chunks = nil

if body = @body
@body = nil
if body.respond_to?(:close)
body.close
end
end

super
end

Expand Down
32 changes: 31 additions & 1 deletion lib/protocol/rack/body/streaming.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,37 @@
module Protocol
module Rack
module Body
Streaming = ::Protocol::HTTP::Body::Streamable::ResponseBody
# Wraps a Rack streaming response body.
# The body must be callable and accept a stream argument.
# This is typically used for Rack hijack responses or bodies wrapped in `Rack::BodyProxy`.
# When closed, this class ensures the wrapped body's `close` method is called if it exists.
class Streaming < ::Protocol::HTTP::Body::Streamable::ResponseBody
# Initialize the streaming body wrapper.
#
# @parameter body [Object] A callable object that accepts a stream argument, such as a Proc or an object that responds to `call`. May optionally respond to `close` for cleanup (e.g., `Rack::BodyProxy`).
# @parameter input [Protocol::HTTP::Body::Readable | Nil] Optional input body for bi-directional streaming.
def initialize(body, input = nil)
@body = body

super
end

# Close the streaming body and clean up resources.
# If the wrapped body responds to `close`, it will be called to allow proper cleanup.
# This ensures that `Rack::BodyProxy` cleanup callbacks are invoked correctly.
#
# @parameter error [Exception | Nil] Optional error that caused the stream to close.
def close(error = nil)
if body = @body
@body = nil
if body.respond_to?(:close)
body.close
end
end

super
end
end
end
end
end
4 changes: 4 additions & 0 deletions releases.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Releases

## Unreleased

- Fix missing `body#close` for streaming bodies.

## v0.21.0

- For the purpose of constructing the rack request environment, trailers are ignored.
Expand Down
25 changes: 25 additions & 0 deletions test/protocol/rack/body/streaming.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,29 @@
expect(body.read).to be == "Hello"
end
end

with "#close" do
it "closes the wrapped body if it responds to close" do
close_called = false
wrapped_body = Object.new
wrapped_body.define_singleton_method(:close) do
close_called = true
end
wrapped_body.define_singleton_method(:call) do |stream|
stream.write("Hello")
end

body = subject.new(wrapped_body)
body.close

expect(close_called).to be == true
end

it "does not fail if wrapped body does not respond to close" do
wrapped_body = proc{|stream| stream.write("Hello")}

body = subject.new(wrapped_body)
body.close
end
end
end
Loading