From d3ef89d8f14c6845ca2f1dab174d9c5a9dd160d6 Mon Sep 17 00:00:00 2001 From: Roman Samoilov <2270393+rsamoilov@users.noreply.github.com> Date: Fri, 9 Oct 2026 12:01:16 +0100 Subject: [PATCH] Gracefully handle requests with no content type --- CHANGELOG.md | 1 + lib/rage/params_parser.rb | 6 +++--- lib/rage/request.rb | 8 ++++++++ spec/params_parser_spec.rb | 9 +++++++++ spec/rage/request_spec.rb | 22 ++++++++++++++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ac4375..e7b2f732 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Update fiber scheduler to support Ruby 4.1 (#397). - Improve blocking mechanism for fibers (#401). - Support `unsubscribe` commands in Action Cable. +- Gracefully handle requests with no content type. ## [1.28.0] - 2026-09-15 diff --git a/lib/rage/params_parser.rb b/lib/rage/params_parser.rb index df423f52..febc4a57 100644 --- a/lib/rage/params_parser.rb +++ b/lib/rage/params_parser.rb @@ -13,11 +13,11 @@ def self.prepare(env, url_params) end end - request_params = if content_type.start_with?("application/json") + request_params = if content_type&.start_with?("application/json") json_parse(env["rack.input"].tap { |io| io.rewind }.read) - elsif content_type.start_with?("application/x-www-form-urlencoded") + elsif content_type&.start_with?("application/x-www-form-urlencoded") || content_type.nil? Iodine::Rack::Utils.parse_urlencoded_nested_query(env["rack.input"].tap { |io| io.rewind }.read) - elsif content_type.start_with?("multipart/form-data") + elsif content_type&.start_with?("multipart/form-data") Iodine::Rack::Utils.parse_multipart(env["rack.input"], content_type) end diff --git a/lib/rage/request.rb b/lib/rage/request.rb index 6dafa1ac..457281d8 100644 --- a/lib/rage/request.rb +++ b/lib/rage/request.rb @@ -223,6 +223,14 @@ def route_uri_pattern end end + # Get the request body stream. + # @return [IO] the request body stream + # @example + # request.body.read # => '{"name":"Rage"}' + def body + @env["rack.input"].tap(&:rewind) + end + private def rack_request diff --git a/spec/params_parser_spec.rb b/spec/params_parser_spec.rb index f8a03523..32b09ead 100644 --- a/spec/params_parser_spec.rb +++ b/spec/params_parser_spec.rb @@ -124,6 +124,15 @@ end end + context "with no content type" do + let(:content_type) { nil } + let(:urlencoded_params) { { slug: "SQjG", parent_ids: %w(4 5), valid: "" } } + + it "parses the body as urlencoded params" do + expect(subject).to equal(urlencoded_params) + end + end + context "with malformed urlencoded body" do let(:urlencoded_params) { { test: true } } diff --git a/spec/rage/request_spec.rb b/spec/rage/request_spec.rb index 9da06167..80279bbe 100644 --- a/spec/rage/request_spec.rb +++ b/spec/rage/request_spec.rb @@ -1,3 +1,7 @@ +# frozen_string_literal: true + +require "stringio" + RSpec.describe Rage::Request do let(:env) do { @@ -270,6 +274,24 @@ expect(request.format).to eq("application/json") end + describe "#body" do + let(:rack_input) { StringIO.new('{"name":"Rage"}') } + + before do + env["rack.input"] = rack_input + end + + it "returns the Rack request body stream" do + expect(request.body).to equal(rack_input) + end + + it "rewinds the body before returning it" do + rack_input.read + + expect(request.body.read).to eq('{"name":"Rage"}') + end + end + it "returns the correct request ID" do expect(request.request_id).to eq("5zfcwiy09bary01l") end