You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pekko.apache.org by fa...@apache.org on 2024/02/24 16:44:57 UTC

(incubator-pekko-grpc) branch main updated: make sure trailers are present in StatusRuntimeException (#230)

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

fanningpj pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-pekko-grpc.git


The following commit(s) were added to refs/heads/main by this push:
     new d6503802 make sure trailers are present in StatusRuntimeException (#230)
d6503802 is described below

commit d65038020103c6d48aff3ed5a5085fb584e88611
Author: João Ferreira <jt...@gmail.com>
AuthorDate: Sat Feb 24 16:44:51 2024 +0000

    make sure trailers are present in StatusRuntimeException (#230)
---
 .../pekko/grpc/internal/PekkoHttpClientUtils.scala | 23 +++++++++---------
 .../grpc/internal/PekkoHttpClientUtilsSpec.scala   | 27 +++++++++++++++++++---
 2 files changed, 36 insertions(+), 14 deletions(-)

diff --git a/runtime/src/main/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtils.scala b/runtime/src/main/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtils.scala
index 01215c2b..b78bc7f5 100644
--- a/runtime/src/main/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtils.scala
+++ b/runtime/src/main/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtils.scala
@@ -24,16 +24,10 @@ import pekko.annotation.InternalApi
 import pekko.event.LoggingAdapter
 import pekko.grpc.GrpcProtocol.GrpcProtocolReader
 import pekko.grpc.{ GrpcClientSettings, GrpcResponseMetadata, GrpcSingleResponse, ProtobufSerializer }
+import pekko.grpc.scaladsl.StringEntry
 import pekko.http.scaladsl.model.HttpEntity.{ Chunk, Chunked, LastChunk, Strict }
 import pekko.http.scaladsl.{ ClientTransport, ConnectionContext, Http }
-import pekko.http.scaladsl.model.{
-  AttributeKey,
-  HttpHeader,
-  HttpRequest,
-  HttpResponse,
-  RequestResponseAssociation,
-  Uri
-}
+import pekko.http.scaladsl.model._
 import pekko.http.scaladsl.settings.ClientConnectionSettings
 import pekko.stream.{ Materializer, OverflowStrategy }
 import pekko.stream.scaladsl.{ Keep, Sink, Source }
@@ -232,7 +226,12 @@ object PekkoHttpClientUtils {
                         .watchTermination()((_, done) =>
                           done.onComplete(_ => trailerPromise.trySuccess(immutable.Seq.empty)))
                     case Strict(_, data) =>
-                      trailerPromise.success(immutable.Seq.empty)
+                      val rawTrailers =
+                        response.attribute(AttributeKeys.trailer).map(_.headers).getOrElse(immutable.Seq.empty)
+                      val trailers = rawTrailers.map(h => HttpHeader.parse(h._1, h._2)).collect {
+                        case HttpHeader.ParsingResult.Ok(header, _) => header
+                      }
+                      trailerPromise.success(trailers)
                       Source.single[ByteString](data)
                     case _ =>
                       response.entity.discardBytes()
@@ -286,12 +285,14 @@ object PekkoHttpClientUtils {
 
   private def mapToStatusException(response: HttpResponse, trailers: Seq[HttpHeader]): StatusRuntimeException = {
     val allHeaders = response.headers ++ trailers
+    val metadata: io.grpc.Metadata =
+      new MetadataImpl(allHeaders.map(h => (h.name, StringEntry(h.value))).toList).toGoogleGrpcMetadata()
     allHeaders.find(_.name == "grpc-status").map(_.value) match {
       case None =>
-        new StatusRuntimeException(mapHttpStatus(response).withDescription("No grpc-status found"))
+        new StatusRuntimeException(mapHttpStatus(response).withDescription("No grpc-status found"), metadata)
       case Some(statusCode) =>
         val description = allHeaders.find(_.name == "grpc-message").map(_.value)
-        new StatusRuntimeException(Status.fromCodeValue(statusCode.toInt).withDescription(description.orNull))
+        new StatusRuntimeException(Status.fromCodeValue(statusCode.toInt).withDescription(description.orNull), metadata)
     }
   }
 
diff --git a/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtilsSpec.scala b/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtilsSpec.scala
index 389b9d82..0915b20d 100644
--- a/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtilsSpec.scala
+++ b/runtime/src/test/scala/org/apache/pekko/grpc/internal/PekkoHttpClientUtilsSpec.scala
@@ -19,12 +19,12 @@ import scala.concurrent.duration._
 import org.apache.pekko
 import pekko.actor.ActorSystem
 import pekko.http.scaladsl.model.HttpEntity.Strict
-import pekko.http.scaladsl.model.HttpResponse
+import pekko.http.scaladsl.model._
 import pekko.http.scaladsl.model.StatusCodes._
 import pekko.http.scaladsl.model.headers.RawHeader
 import pekko.testkit.TestKit
 import pekko.util.ByteString
-import io.grpc.{ Status, StatusRuntimeException }
+import io.grpc.{ Metadata, Status, StatusRuntimeException }
 import org.scalatest.concurrent.ScalaFutures
 import org.scalatest.matchers.should.Matchers
 import org.scalatest.time.Span
@@ -47,12 +47,33 @@ class PekkoHttpClientUtilsSpec extends TestKit(ActorSystem()) with AnyWordSpecLi
     }
 
     "map a strict 200 response with non-0 gRPC error code to a failed stream" in {
+      val responseHeaders = List(RawHeader("grpc-status", "9"), RawHeader("custom-key", "custom-value-in-header"))
+      val response =
+        Future.successful(HttpResponse(OK, responseHeaders, Strict(GrpcProtocolNative.contentType, ByteString.empty)))
+      val source = PekkoHttpClientUtils.responseToSource(response, null)
+
+      val failure = source.run().failed.futureValue
+      failure.asInstanceOf[StatusRuntimeException].getStatus.getCode should be(Status.Code.FAILED_PRECONDITION)
+      failure.asInstanceOf[StatusRuntimeException].getTrailers.get(key) should be("custom-value-in-header")
+    }
+
+    "map a strict 200 response with non-0 gRPC error code with a trailer to a failed stream with trailer metadata" in {
+      val responseHeaders = List(RawHeader("grpc-status", "9"))
+      val responseTrailers = Trailer(RawHeader("custom-key", "custom-trailer-value") :: Nil)
       val response = Future.successful(
-        HttpResponse(OK, List(RawHeader("grpc-status", "9")), Strict(GrpcProtocolNative.contentType, ByteString.empty)))
+        new HttpResponse(
+          OK,
+          responseHeaders,
+          Map.empty[AttributeKey[_], Any].updated(AttributeKeys.trailer, responseTrailers),
+          Strict(GrpcProtocolNative.contentType, ByteString.empty),
+          HttpProtocols.`HTTP/1.1`))
       val source = PekkoHttpClientUtils.responseToSource(response, null)
 
       val failure = source.run().failed.futureValue
       failure.asInstanceOf[StatusRuntimeException].getStatus.getCode should be(Status.Code.FAILED_PRECONDITION)
+      failure.asInstanceOf[StatusRuntimeException].getTrailers.get(key) should be("custom-trailer-value")
     }
+
+    lazy val key = Metadata.Key.of("custom-key", Metadata.ASCII_STRING_MARSHALLER)
   }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pekko.apache.org
For additional commands, e-mail: commits-help@pekko.apache.org