lib/protocol/rack/rewindable.rb
# frozen_string_literal: true # Released under the MIT License. # Copyright, 2022-2025, by Samuel Williams. require "protocol/http/body/rewindable" require "protocol/http/middleware" module Protocol module Rack # Content-type driven input buffering, specific to the needs of `rack`. # This middleware ensures that request bodies for certain content types # can be read multiple times, which is required by Rack's specification. class Rewindable < ::Protocol::HTTP::Middleware # Media types that require buffering. # These types typically contain form data or file uploads that may need # to be read multiple times by Rack applications. BUFFERED_MEDIA_TYPES = %r{ application/x-www-form-urlencoded| multipart/form-data| multipart/related| multipart/mixed }x # The HTTP POST method. POST = "POST" # Initialize the rewindable middleware. # # @parameter app [Protocol::HTTP::Middleware] The middleware to wrap. def initialize(app) super(app) end # Determine whether the request needs a rewindable body. # A request needs a rewindable body if: # - It's a POST request with no content type (legacy behavior) # - It has a content type that matches BUFFERED_MEDIA_TYPES # # @parameter request [Protocol::HTTP::Request] The request to check. # @returns [Boolean] True if the request body should be rewindable. def needs_rewind?(request) content_type = request.headers["content-type"] if request.method == POST and content_type.nil? return true end if BUFFERED_MEDIA_TYPES =~ content_type return true end return false end # Create a Rack environment from the request. # Delegates to the wrapped middleware. # # @parameter request [Protocol::HTTP::Request] The request to create an environment from. # @returns [Hash] The Rack environment hash. def make_environment(request) @delegate.make_environment(request) end # Wrap the request body in a rewindable buffer if required. # If the request needs a rewindable body, wraps it in a {Protocol::HTTP::Body::Rewindable}. # # @parameter request [Protocol::HTTP::Request] The request to process. # @returns [Protocol::HTTP::Response] The response from the wrapped middleware. def call(request) if body = request.body and needs_rewind?(request) request.body = Protocol::HTTP::Body::Rewindable.new(body) end return super end end end end