This is an automated email from the ASF dual-hosted git repository.

pjfanning pushed a commit to branch 1.3.x
in repository https://gitbox.apache.org/repos/asf/pekko-grpc.git


The following commit(s) were added to refs/heads/1.3.x by this push:
     new a6009af5 Fix Codecs.negotiate() dropping supported encodings with 
whitespace (#851)
a6009af5 is described below

commit a6009af530e2de22c212d5190e9a9c5a790a6313
Author: PJ Fanning <[email protected]>
AuthorDate: Fri Aug 21 10:11:31 2026 +0100

    Fix Codecs.negotiate() dropping supported encodings with whitespace (#851)
    
    * Fix Codecs.negotiate() dropping supported encodings with whitespace (#850)
    
    Motivation:
    grpc-accept-encoding headers formatted with a space after the comma
    (e.g. "deflate, gzip", as sent by grpc-go/grpc-python/grpcurl) were
    never matched against supported codec names, silently downgrading
    negotiation to Identity even when the client offered gzip.
    
    Modification:
    Trim each split token in `Message-Accept-Encoding`.findIn before
    codec-name lookup.
    
    Result:
    Codecs.negotiate now correctly picks a supported codec regardless of
    whitespace around commas in grpc-accept-encoding.
    
    Tests:
    - sbt "runtime / testOnly org.apache.pekko.grpc.CodecsSpec" - pass
    
    References:
    Fixes #847
    
    * missing import
    
    * csvparser util for headers (#852)
    
    * csvparser util for headers
    
    * Update headers.scala
    
    ---------
    
    Co-authored-by: haroldb1 <[email protected]>
---
 .../apache/pekko/grpc/scaladsl/headers/headers.scala  | 19 +++++++++++++++----
 .../test/scala/org/apache/pekko/grpc/CodecsSpec.scala | 19 +++++++++++++++++++
 2 files changed, 34 insertions(+), 4 deletions(-)

diff --git 
a/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/headers/headers.scala 
b/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/headers/headers.scala
index d607b943..fe58f26d 100644
--- 
a/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/headers/headers.scala
+++ 
b/runtime/src/main/scala/org/apache/pekko/grpc/scaladsl/headers/headers.scala
@@ -23,6 +23,14 @@ import scala.collection.compat.immutable.ArraySeq
 import scala.collection.immutable
 import scala.util.Try
 
+/**
+ * Simple CSV parser for HTTP header values. Not meant to be a full CSV parser,
+ * just enough to parse the headers we care about.
+ */
+private object SimpleCSVParser {
+  def parse(value: String): Array[String] = value.split(',').map(_.trim)
+}
+
 @ApiMayChange
 final class `Message-Accept-Encoding`(override val value: String)
     extends ModeledCustomHeader[`Message-Accept-Encoding`] {
@@ -30,7 +38,7 @@ final class `Message-Accept-Encoding`(override val value: 
String)
   override def renderInResponses = true
   override val companion = `Message-Accept-Encoding`
 
-  lazy val values: Array[String] = value.split(',')
+  lazy val values: Array[String] = SimpleCSVParser.parse(value)
 }
 
 @ApiMayChange
@@ -42,7 +50,9 @@ object `Message-Accept-Encoding` extends 
ModeledCustomHeaderCompanion[`Message-A
     Try(new `Message-Accept-Encoding`(value))
 
   def findIn(headers: Iterable[jm.HttpHeader]): Array[String] =
-    headers.collectFirst { case h if h.is(name) => h.value().split(',') 
}.getOrElse(Array.empty)
+    headers.collectFirst {
+      case h if h.is(name) => SimpleCSVParser.parse(h.value())
+    }.getOrElse(Array.empty)
 
   /** Java API */
   def findIn(headers: java.lang.Iterable[jm.HttpHeader]): Array[String] = {
@@ -131,10 +141,11 @@ private[grpc] object `Trailer` extends 
ModeledCustomHeaderCompanion[`Trailer`] {
 
   override val lowercaseName: String = super.lowercaseName
 
-  override def parse(value: String): Try[`Trailer`] = 
Try(`Trailer`(ArraySeq.unsafeWrapArray(value.split(','))))
+  override def parse(value: String): Try[`Trailer`] =
+    Try(`Trailer`(ArraySeq.unsafeWrapArray(SimpleCSVParser.parse(value))))
 
   def findIn(headers: immutable.Seq[HttpHeader]): 
Option[immutable.Seq[String]] =
     headers.collectFirst {
-      case header if header.is(name) => 
ArraySeq.unsafeWrapArray(header.value().split(',').map(_.trim))
+      case header if header.is(name) => 
ArraySeq.unsafeWrapArray(SimpleCSVParser.parse(header.value()))
     }
 }
diff --git a/runtime/src/test/scala/org/apache/pekko/grpc/CodecsSpec.scala 
b/runtime/src/test/scala/org/apache/pekko/grpc/CodecsSpec.scala
index c5755913..0d323788 100644
--- a/runtime/src/test/scala/org/apache/pekko/grpc/CodecsSpec.scala
+++ b/runtime/src/test/scala/org/apache/pekko/grpc/CodecsSpec.scala
@@ -16,6 +16,7 @@ import org.apache.pekko
 import pekko.grpc.internal.{ Codecs, Gzip, Identity }
 import pekko.grpc.scaladsl.headers
 import pekko.http.scaladsl.model.HttpRequest
+import pekko.http.scaladsl.model.headers.RawHeader
 import io.grpc.Status
 import org.scalatest.matchers.should.Matchers
 import org.scalatest.wordspec.AnyWordSpec
@@ -58,6 +59,24 @@ class CodecsSpec extends AnyWordSpec with Matchers with 
TryValues {
       Codecs.negotiate(accept("xxxxx")) should be(Identity)
     }
 
+    // Regression test: akka-grpc #1897 — request.header[T] silently returns 
None for
+    // ModeledCustomHeader types, which would break compression negotiation.
+    // Our implementation uses findIn on raw headers instead, which works 
correctly.
+    "negotiate gzip from raw headers (not typed custom headers)" in {
+      val request = HttpRequest(headers = 
immutable.Seq(RawHeader("grpc-accept-encoding", "gzip")))
+      Codecs.negotiate(request) should be(Gzip)
+    }
+
+    "negotiate from raw headers with multiple encodings" in {
+      val request = HttpRequest(headers = 
immutable.Seq(RawHeader("grpc-accept-encoding", "gzip,identity")))
+      Codecs.negotiate(request) should be(Gzip)
+    }
+
+    "negotiate gzip when grpc-accept-encoding uses comma+space separators (as 
sent by grpc-go/grpc-python/grpcurl)" in {
+      val request = HttpRequest(headers = 
immutable.Seq(RawHeader("grpc-accept-encoding", "deflate, gzip")))
+      Codecs.negotiate(request) should be(Gzip)
+    }
+
   }
 
   "Detecting message encoding from remote" should {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to