Repository: newrelic/newrelic-ruby-agent (Ruby)

Issue:
Replace Rails `content_type` header with `media_type`
A change in Rails 6.1 -> Rails 7 includes [ActionDispatch::Request#content_type now returns Content-Type header as it is](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#actiondispatch-request-content-type-now-returns-content-type-header-as-it-is).

Where the call `request.content_type` used to return `'text/csv'` in Rails 6, it now returns the entire header, something like `'text/csv;header=present;charset=utf-16'` in Rails 7. To return just the MIME type, one woud call `request.media_type`. This is what the agent is expected to send via [spec](https://source.datanerd.us/agents/agent-specs/blob/main/APIs/web_transaction.md#web-request-and-response-functionality). [Definition](https://learn.microsoft.com/en-us/previous-versions/iis/6.0-sdk/ms525208(v=vs.90)?redirectedfrom=MSDN).

We should replace calls to `request.content_type` with `request.media_type`. We should confirm `request.media_type` exists in older Rails versions. Already confirmed in Rails 6. 

Discovered in #2500



Discussion:
https://new-relic.atlassian.net/browse/NR-248625



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d028e81bd5..95151a76ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@
 
 ## dev
 
-Version <dev> adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration-based, automatic way to add custom instrumentation method tracers, fixes a JRuby bug in the configuration manager, and fixes a bug related to `Bundler.rubygems.installed_specs`.
+Version <dev> adds Apache Kafka instrumentation for the rdkafka and ruby-kafka gems, introduces a configuration-based, automatic way to add custom instrumentation method tracers, correctly captures MIME type for AcionDispatch 7.0+ requests, fixes a JRuby bug in the configuration manager, and fixes a bug related to `Bundler.rubygems.installed_specs`.
 
 - **Feature: Add Apache Kafka instrumentation for the rdkafka and ruby-kafka gems**
 
@@ -56,6 +56,10 @@ Version <dev> adds Apache Kafka instrumentation for the rdkafka and ruby-kafka g
 
 [PR#2851](https://github.com/newrelic/newrelic-ruby-agent/pull/2851)
 
+- **Feature: Collect just MIME type for AcionDispatch 7.0+ requests**
+  
+  Rails 7.0 [introduced changes](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#actiondispatch-request-content-type-now-returns-content-type-header-as-it-is) to the behavior of `ActionDispatch::Request#content_type`, adding extra request-related details the agent wasn't expecting to collect. Additionally, the agent's use of `content_type ` was triggering deprecation warnings. The agent now uses `ActionDispatch::Request#media_type` to capture the MIME type. Thanks to [@internethostage](https://github.com/internethostage) for letting us know about this change. [Issue#2500](https://github.com/newrelic/newrelic-ruby-agent/issues/2500) [PR#2855](https://github.com/newrelic/newrelic-ruby-agent/pull/2855)
+
 - **Bugfix: Corrected Boolean coercion for `newrelic.yml` configuration**
 
   Previously, any String assigned to New Relic configurations expecting a Boolean value were evaluated as `true`. This could lead to unexpected behavior. For example, setting `application_logging.enabled: 'false'` in `newrelic.yml` would incorrectly evaluate to `application_logging.enabled: true` due to the truthy nature of Strings. 
diff --git a/lib/new_relic/agent/transaction/request_attributes.rb b/lib/new_relic/agent/transaction/request_attributes.rb
index 60486ae94c..def6f07183 100644
--- a/lib/new_relic/agent/transaction/request_attributes.rb
+++ b/lib/new_relic/agent/transaction/request_attributes.rb
@@ -24,7 +24,7 @@ def initialize(request)
           @referer = referer_from_request(request)
           @accept = attribute_from_env(request, HTTP_ACCEPT_HEADER_KEY)
           @content_length = content_length_from_request(request)
-          @content_type = attribute_from_request(request, :content_type)
+          @content_type = content_type_attribute_from_request(request)
           @host = attribute_from_request(request, :host)
           @port = port_from_request(request)
           @user_agent = attribute_from_request(request, :user_agent)
@@ -127,6 +127,18 @@ def attribute_from_request(request, attribute_method)
           end
         end
 
+        def content_type_attribute_from_request(request)
+          # Rails 7.0 changed the behavior of `content_type`. We want just the MIME type, so use `media_type` if available.
+          # https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#actiondispatch-request-content-type-now-returns-content-type-header-as-it-is
+          content_type = if request.respond_to?(:media_type)
+            :media_type
+          elsif request.respond_to?(:content_type)
+            :content_type
+          end
+
+          request.send(content_type) if content_type
+        end
+
         def attribute_from_env(request, key)
           if env = attribute_from_request(request, :env)
             env[key]


Test patch:
diff --git a/test/new_relic/agent/transaction/request_attributes_test.rb b/test/new_relic/agent/transaction/request_attributes_test.rb
index 5ed1baa593..20ccb7115e 100644
--- a/test/new_relic/agent/transaction/request_attributes_test.rb
+++ b/test/new_relic/agent/transaction/request_attributes_test.rb
@@ -127,13 +127,42 @@ def test_sets_content_length_from_request
           assert_equal 111, attrs.content_length
         end
 
-        def test_sets_content_type_from_request
+        def test_sets_content_type_from_request_content_type_attribute
           request = stub('request', :content_type => 'application/json')
           attrs = RequestAttributes.new(request)
 
           assert_equal 'application/json', attrs.content_type
         end
 
+        def test_sets_content_type_from_request_media_type_attribute
+          media_type = 'pool-party/alligator'
+          request = stub('request', media_type: media_type)
+          attrs = RequestAttributes.new(request)
+
+          assert_equal media_type, attrs.content_type
+        end
+
+        def test_sets_content_type_to_nil_if_media_type_is_available_with_a_nil_value
+          request = stub('request', media_type: nil)
+          attrs = RequestAttributes.new(request)
+
+          assert_nil attrs.content_type
+        end
+
+        def test_sets_content_type_to_nil_if_content_type_is_available_with_a_nil_value
+          request = stub('request', content_type: nil)
+          attrs = RequestAttributes.new(request)
+
+          assert_nil attrs.content_type
+        end
+
+        def test_sets_content_type_to_nil_if_neither_media_type_or_content_type_are_available
+          request = stub('request')
+          attrs = RequestAttributes.new(request)
+
+          assert_nil attrs.content_type
+        end
+
         def test_sets_host_from_request
           request = stub('request', :host => 'localhost')
           attrs = RequestAttributes.new(request)