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]