lib/protocol/rack/request.rb



# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2022-2025, by Samuel Williams.

require "protocol/http/request"
require "protocol/http/headers"

require_relative "constants"
require_relative "body/input_wrapper"

module Protocol
	module Rack
		# A Rack-compatible HTTP request wrapper.
		# This class provides a bridge between Rack's environment hash and Protocol::HTTP::Request.
		# It handles conversion of Rack environment variables to HTTP request properties.
		class Request < ::Protocol::HTTP::Request
			# Get or create a Request instance for the given Rack environment.
			# The request is cached in the environment to avoid creating multiple instances.
			# 
			# @parameter env [Hash] The Rack environment hash.
			# @returns [Request] A Request instance for the environment.
			def self.[](env)
				env["protocol.http.request"] ||= new(env)
			end

			# Initialize a new Request instance from a Rack environment.
			# 
			# @parameter env [Hash] The Rack environment hash.
			def initialize(env)
				@env = env

				super(
					@env["rack.url_scheme"],
					@env["HTTP_HOST"],
					@env["REQUEST_METHOD"],
					@env["PATH_INFO"],
					@env["SERVER_PROTOCOL"],
					self.class.headers(@env),
					Body::InputWrapper.new(@env["rack.input"]),
					self.class.protocol(@env)
				)
			end

			# Extract the protocol list from the Rack environment.
			# Checks both `rack.protocol` and `HTTP_UPGRADE` headers.
			# 
			# @parameter env [Hash] The Rack environment hash.
			# @returns [Array(String) | Nil] The list of protocols or `nil` if none specified.
			def self.protocol(env)
				if protocols = env["rack.protocol"]
					return Array(protocols)
				elsif protocols = env[CGI::HTTP_UPGRADE]
					return protocols.split(/\s*,\s*/)
				end
			end

			# Extract HTTP headers from the Rack environment.
			# Converts Rack's `HTTP_*` environment variables to proper HTTP headers.
			# 
			# @parameter env [Hash] The Rack environment hash.
			# @returns [Protocol::HTTP::Headers] The extracted HTTP headers.
			def self.headers(env)
				headers = ::Protocol::HTTP::Headers.new
				env.each do |key, value|
					if key.start_with?("HTTP_")
						next if key == "HTTP_HOST"
						headers[key[5..-1].gsub("_", "-").downcase] = value
					end
				end

				return headers
			end
		end
	end
end