Protocol::RackSourceProtocolRackBody

module Body

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

Nested

Definitions

CONTENT_LENGTH = "content-length"

The content-length header key.

def self.no_content?(status)

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.

Signature

parameter status Integer

The HTTP status code.

returns Boolean

True if the status code indicates no content.

Implementation

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

def self.wrap(env, status, headers, body, input = nil, head = false)

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

Signature

parameter env Hash

The Rack environment.

parameter status Integer

The HTTP status code.

parameter headers Hash

The response headers.

parameter body Object

The response body to wrap.

parameter input Object

Optional input for streaming bodies.

parameter head Boolean

Indicates if this is a HEAD request, which should not have a body.

returns Protocol::HTTP::Body

The wrapped response body.

Implementation

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

def self.completion_callback(response_finished, env, status, headers)

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

Signature

parameter response_finished Array

Array of response finished callbacks.

parameter env Hash

The Rack environment.

parameter status Integer

The HTTP status code.

parameter headers Hash

The response headers.

returns Proc

A callback that calls all response finished handlers.

Implementation

def self.completion_callback(response_finished, env, status, headers)
	proc do |error|
		response_finished.each do |callback|
			callback.call(env, status, headers, error)
		end
	end
end