Protocol::RackGuidesRequest and Response Handling

Request and Response Handling

This guide explains how to work with requests and responses when bridging between Rack and Protocol::HTTP, covering advanced use cases and edge cases.

Request Conversion

The class Protocol::Rack::Request class converts Rack environment hashes into rich Protocol::HTTP request objects, providing access to modern HTTP features while maintaining compatibility with Rack.

Basic Request Access

require "protocol/rack/request"

run do |env|
	request = Protocol::Rack::Request[env]
	
	# Access request properties:
	puts request.method         # "GET", "POST", etc.
	puts request.path           # "/users/123"
	puts request.url_scheme     # "http" or "https"
	puts request.authority      # "example.com:80"
end

Headers

Headers are automatically extracted from Rack's HTTP_* environment variables:

run do |env|
	request = Protocol::Rack::Request[env]
	
	# Headers are available as a `Protocol::HTTP::Headers` object:
	user_agent = request.headers["user-agent"]
	content_type = request.headers["content-type"]
	
	# Headers are case-insensitive:
	user_agent = request.headers["User-Agent"]  # Same as above
end

The adapter converts Rack's HTTP_ACCEPT_ENCODING format to standard HTTP header names (accept-encoding).

Request Body

The request body is wrapped in a Protocol::HTTP-compatible interface:

run do |env|
	request = Protocol::Rack::Request[env]
	
	# Read the entire body:
	body = request.body.read
	
	# Or stream it:
	request.body.each do |chunk|
		process_chunk(chunk)
	end
	
	# The body supports rewind if the underlying Rack input supports it:
	request.body.rewind
end

The body wrapper handles Rack's rack.input interface, which may or may not support rewind depending on the server.

Query Parameters

Query parameters are parsed from the request path:

run do |env|
	request = Protocol::Rack::Request[env]
	
	# Access query string:
	query = request.query  # "name=value&other=123"
	
	# Parse query parameters (if using a helper):
	params = URI.decode_www_form(query).to_h
end

Protocol Upgrades

The adapter handles protocol upgrade requests (like WebSockets):

run do |env|
	request = Protocol::Rack::Request[env]
	
	# Check for upgrade protocols:
	if protocols = request.protocol
		# protocols is an array: ["websocket"]:
		if protocols.include?("websocket")
			# Handle WebSocket upgrade.
		end
	end
end

Protocols are extracted from either rack.protocol or the HTTP_UPGRADE header.

Response Conversion

The class Protocol::Rack::Response class and Protocol::Rack::Adapter.make_response handle converting Protocol::HTTP responses back to Rack format.

Basic Response

require "protocol/rack/adapter"

run do |env|
	request = Protocol::Rack::Request[env]
	
	# Create a `Protocol::HTTP` response:
	response = Protocol::HTTP::Response[
		200,
		{"content-type" => "text/html"},
		["<h1>Hello</h1>"]
	]
	
	# Convert to Rack format:
	Protocol::Rack::Adapter.make_response(env, response)
end

Response Bodies

The adapter handles different types of response bodies:

Enumerable Bodies

# Array bodies:
response = Protocol::HTTP::Response[
	200,
	{"content-type" => "text/plain"},
	["Hello", " ", "World"]
]

# Enumerable bodies:
response = Protocol::HTTP::Response[
	200,
	{"content-type" => "text/plain"},
	Enumerator.new do |yielder|
		yielder << "Chunk 1\n"
		yielder << "Chunk 2\n"
	end
]

Streaming Bodies

# Streaming response body:
body = Protocol::HTTP::Body::Buffered.new(["Streaming content"])

response = Protocol::HTTP::Response[
	200,
	{"content-type" => "text/plain"},
	body
]

File Bodies

# File-based responses:
body = Protocol::HTTP::Body::File.open("path/to/file.txt")

response = Protocol::HTTP::Response[
	200,
	{"content-type" => "text/plain"},
	body
]

HEAD Requests

The adapter automatically handles HEAD requests by removing response bodies:

run do |env|
	request = Protocol::Rack::Request[env]
	
	# Create a response with a body:
	response = Protocol::HTTP::Response[
		200,
		{"content-type" => "text/html"},
		["<h1>Full Response</h1>"]
	]
	
	# For HEAD requests, the body is automatically removed:
	Protocol::Rack::Adapter.make_response(env, response)
end

Status Codes Without Bodies

Certain status codes (204 No Content, 205 Reset Content, 304 Not Modified) should not include response bodies. The adapter handles this automatically:

response = Protocol::HTTP::Response[
	204,  # No Content
	{},
	["This body will be removed"]
]

# The adapter automatically removes the body for 204 responses.

Rack-Specific Features

Hijacking

Rack supports response hijacking, which allows taking over the connection:

# In a Rack application:
[200, {"rack.hijack" => proc{|io| io.write("Hijacked!")}}, []]

# The adapter handles hijacking automatically using streaming responses.

Response Finished Callbacks

Rack 2+ supports rack.response_finished callbacks:

env["rack.response_finished"] ||= []
env["rack.response_finished"] << proc do |env, status, headers, error|
	# Cleanup or logging after response is sent
	puts "Response finished: #{status}"
end

The adapter invokes these callbacks in reverse order of registration, as specified by the Rack specification.

Hop Headers

HTTP hop-by-hop headers (like Connection, Transfer-Encoding) are automatically removed from responses, as they should not be forwarded through proxies:

response = Protocol::HTTP::Response[
	200,
	{
		"content-type" => "text/plain",
		"connection" => "close",          # This will be removed
		"transfer-encoding" => "chunked"  # This will be removed
	},
	["Body"]
]