Module: Protocol::Rack::Body

Defined in:
lib/protocol/rack/body.rb,
lib/protocol/rack/body/streaming.rb,
lib/protocol/rack/body/enumerable.rb,
lib/protocol/rack/body/input_wrapper.rb

Overview

The Body module provides functionality for handling Rack response bodies. It includes methods for wrapping different types of response bodies and handling completion callbacks.

Defined Under Namespace

Classes: Enumerable, InputWrapper

Constant Summary collapse

CONTENT_LENGTH =

The ‘content-length` header key.

"content-length"
Streaming =
::Protocol::HTTP::Body::Streamable::ResponseBody

Class Method Summary collapse

Class Method Details

.completion_callback(response_finished, env, status, headers) ⇒ Object

Create a completion callback for response finished handlers. The callback is called with any error that occurred during response processing.

Callbacks are invoked in reverse order of registration, as specified by the Rack specification. If a callback raises an exception, it is caught and logged, but does not prevent other callbacks from being invoked.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/protocol/rack/body.rb', line 117

def self.completion_callback(response_finished, env, status, headers)
	proc do |error|
		# Invoke callbacks in reverse order of registration, as specified by the Rack specification.
		response_finished.reverse_each do |callback|
			begin
				callback.call(env, status, headers, error)
			rescue => callback_error
				# If a callback raises an exception, log it but continue invoking other callbacks.
				# The Rack specification states that callbacks should not raise exceptions, but we handle
				# this gracefully to prevent one misbehaving callback from breaking others.
				Console.error(self, "Error occurred during response finished callback:", callback_error)
			end
		end
	end
end

.no_content?(status) ⇒ Boolean

Check if the given status code indicates no content should be returned. Status codes 204 (No Content), 205 (Reset Content), and 304 (Not Modified) should not include a response body.

Returns:

  • (Boolean)


27
28
29
# File 'lib/protocol/rack/body.rb', line 27

def self.no_content?(status)
	status == 204 or status == 205 or status == 304
end

.wrap(env, status, headers, body, input = nil, head = false) ⇒ Object

Wrap a Rack response body into a HTTP::Body instance. Handles different types of response bodies:

  • HTTP::Body::Readable instances are returned as-is.

  • Bodies that respond to ‘to_path` are wrapped in HTTP::Body::File.

  • Enumerable bodies are wrapped in Enumerable.

  • Other bodies are wrapped in Streaming.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/protocol/rack/body.rb', line 45

def self.wrap(env, status, headers, body, input = nil, head = false)
	# In no circumstance do we want this header propagating out:
	if length = headers.delete(CONTENT_LENGTH)
		# We don't really trust the user to provide the right length to the transport.
		length = Integer(length)
	end
	
	# If we have an Async::HTTP body, we return it directly:
	if body.is_a?(::Protocol::HTTP::Body::Readable)
		# Ignore.
	elsif status == 200 and body.respond_to?(:to_path)
		begin
			# Don't mangle partial responses (206)
			body = ::Protocol::HTTP::Body::File.open(body.to_path).tap do
				body.close if body.respond_to?(:close) # Close the original body.
			end
		rescue Errno::ENOENT
			# If the file is not available, ignore.
		end
	elsif body.respond_to?(:each)
		body = Body::Enumerable.wrap(body, length)
	elsif body
		body = Body::Streaming.new(body, input)
	else
		Console.warn(self, "Rack response body was nil, ignoring!")
	end
	
	if body and no_content?(status)
		unless body.empty?
			Console.warn(self, "Rack response body was not empty, and status code indicates no content!", body: body, status: status)
		end
		
		body.close
		body = nil
	end
	
	response_finished = env[RACK_RESPONSE_FINISHED]
	
	if response_finished&.any?
		if body
			body = ::Protocol::HTTP::Body::Completable.new(body, completion_callback(response_finished, env, status, headers))
		else
			completion_callback(response_finished, env, status, headers).call(nil)
		end
	end
	
	# There are two main situations we need to handle:
	# 1. The application has the `Rack::Head` middleware in the stack, which means we should not return a body, and the application is also responsible for setting the content-length header. `Rack::Head` will result in an empty enumerable body.
	# 2. The application does not have `Rack::Head`, in which case it will return a body and we need to extract the length.
	# In both cases, we need to ensure that the body is wrapped correctly. If there is no body and we don't know the length, we also just return `nil`.
	if head
		if body
			body = ::Protocol::HTTP::Body::Head.for(body)
		elsif length
			body = ::Protocol::HTTP::Body::Head.new(length)
		end
		# Otherwise, body is `nil` and we don't know the length either.
	end
	
	return body
end