Skip to content

Commit 65989ff

Browse files
authored
fix: Include metadata in StatusRuntimeException (#1906)
1 parent 701dce6 commit 65989ff

File tree

2 files changed

+44
-15
lines changed

2 files changed

+44
-15
lines changed

runtime/src/main/scala/akka/grpc/internal/AkkaHttpClientUtils.scala

+18-11
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import akka.annotation.InternalApi
1414
import akka.event.LoggingAdapter
1515
import akka.grpc.GrpcProtocol.GrpcProtocolReader
1616
import akka.grpc.{ GrpcClientSettings, GrpcResponseMetadata, GrpcSingleResponse, ProtobufSerializer }
17+
import akka.grpc.scaladsl.StringEntry
1718
import akka.http.scaladsl.model.HttpEntity.{ Chunk, Chunked, LastChunk, Strict }
1819
import akka.http.scaladsl.{ ClientTransport, ConnectionContext, Http }
19-
import akka.http.scaladsl.model.{ AttributeKey, HttpHeader, HttpRequest, HttpResponse, RequestResponseAssociation, Uri }
20+
import akka.http.scaladsl.model._
2021
import akka.http.scaladsl.settings.ClientConnectionSettings
2122
import akka.stream.{ Materializer, OverflowStrategy }
2223
import akka.stream.scaladsl.{ Keep, Sink, Source }
@@ -220,7 +221,11 @@ object AkkaHttpClientUtils {
220221
.watchTermination()((_, done) =>
221222
done.onComplete(_ => trailerPromise.trySuccess(immutable.Seq.empty)))
222223
case Strict(_, data) =>
223-
trailerPromise.success(immutable.Seq.empty)
224+
val rawTrailers = response.attribute(AttributeKeys.trailer).map(_.headers).getOrElse(Seq.empty)
225+
val trailers = rawTrailers.map(h => HttpHeader.parse(h._1, h._2)).collect {
226+
case HttpHeader.ParsingResult.Ok(header, _) => header
227+
}
228+
trailerPromise.success(trailers)
224229
Source.single[ByteString](data)
225230
case _ =>
226231
response.entity.discardBytes()
@@ -281,19 +286,21 @@ object AkkaHttpClientUtils {
281286
response: HttpResponse,
282287
trailers: Seq[HttpHeader]): StatusRuntimeException = {
283288
val allHeaders = response.headers ++ trailers
289+
val metadata: io.grpc.Metadata =
290+
new MetadataImpl(allHeaders.map(h => (h.name, StringEntry(h.value))).toList).toGoogleGrpcMetadata()
284291
allHeaders.find(_.name == "grpc-status").map(_.value) match {
285292
case None =>
286-
new StatusRuntimeException(
287-
mapHttpStatus(response)
288-
.withDescription("No grpc-status found")
289-
.augmentDescription(s"When calling rpc service: ${requestUri.toString()}"))
293+
val status = mapHttpStatus(response)
294+
.withDescription("No grpc-status found")
295+
.augmentDescription(s"When calling rpc service: ${requestUri.toString()}")
296+
new StatusRuntimeException(status, metadata)
290297
case Some(statusCode) =>
291298
val description = allHeaders.find(_.name == "grpc-message").map(_.value)
292-
new StatusRuntimeException(
293-
Status
294-
.fromCodeValue(statusCode.toInt)
295-
.withDescription(description.orNull)
296-
.augmentDescription(s"When calling rpc service: ${requestUri.toString()}"))
299+
val status = Status
300+
.fromCodeValue(statusCode.toInt)
301+
.withDescription(description.orNull)
302+
.augmentDescription(s"When calling rpc service: ${requestUri.toString()}")
303+
new StatusRuntimeException(status, metadata)
297304
}
298305
}
299306

runtime/src/test/scala/akka/grpc/internal/AkkaHttpClientUtilsSpec.scala

+26-4
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@ package akka.grpc.internal
77
import scala.concurrent.Future
88
import scala.concurrent.duration._
99
import akka.actor.ActorSystem
10+
import akka.http.scaladsl.model._
1011
import akka.http.scaladsl.model.HttpEntity.Strict
11-
import akka.http.scaladsl.model.HttpResponse
1212
import akka.http.scaladsl.model.StatusCodes._
13-
import akka.http.scaladsl.model.Uri
1413
import akka.http.scaladsl.model.headers.RawHeader
1514
import akka.testkit.TestKit
1615
import akka.util.ByteString
17-
import io.grpc.{ Status, StatusRuntimeException }
16+
import io.grpc.{ Metadata, Status, StatusRuntimeException }
1817
import org.scalatest.concurrent.ScalaFutures
1918
import org.scalatest.matchers.should.Matchers
2019
import org.scalatest.time.Span
@@ -41,12 +40,35 @@ class AkkaHttpClientUtilsSpec extends TestKit(ActorSystem()) with AnyWordSpecLik
4140

4241
"map a strict 200 response with non-0 gRPC error code to a failed stream" in {
4342
val requestUri = Uri("https://example.com/GuestExeSample/GrpcHello")
43+
val responseHeaders = List(RawHeader("grpc-status", "9"), RawHeader("custom-key", "custom-value-in-header"))
44+
val response =
45+
Future.successful(HttpResponse(OK, responseHeaders, Strict(GrpcProtocolNative.contentType, ByteString.empty)))
46+
val source = AkkaHttpClientUtils.responseToSource(requestUri, response, null, false)
47+
48+
val failure = source.run().failed.futureValue
49+
failure.asInstanceOf[StatusRuntimeException].getStatus.getCode should be(Status.Code.FAILED_PRECONDITION)
50+
failure.asInstanceOf[StatusRuntimeException].getTrailers.get(key) should be("custom-value-in-header")
51+
}
52+
53+
"map a strict 200 response with non-0 gRPC error code with a trailer to a failed stream with trailer metadata" in {
54+
val requestUri = Uri("https://example.com/GuestExeSample/GrpcHello")
55+
val responseHeaders = List(RawHeader("grpc-status", "9"))
56+
val responseTrailers = Trailer(RawHeader("custom-key", "custom-trailer-value") :: Nil)
4457
val response = Future.successful(
45-
HttpResponse(OK, List(RawHeader("grpc-status", "9")), Strict(GrpcProtocolNative.contentType, ByteString.empty)))
58+
new HttpResponse(
59+
OK,
60+
responseHeaders,
61+
Map.empty[AttributeKey[_], Any].updated(AttributeKeys.trailer, responseTrailers),
62+
Strict(GrpcProtocolNative.contentType, ByteString.empty),
63+
HttpProtocols.`HTTP/1.1`))
4664
val source = AkkaHttpClientUtils.responseToSource(requestUri, response, null, false)
4765

4866
val failure = source.run().failed.futureValue
4967
failure.asInstanceOf[StatusRuntimeException].getStatus.getCode should be(Status.Code.FAILED_PRECONDITION)
68+
failure.asInstanceOf[StatusRuntimeException].getTrailers should not be null
69+
failure.asInstanceOf[StatusRuntimeException].getTrailers.get(key) should be("custom-trailer-value")
5070
}
71+
72+
lazy val key = Metadata.Key.of("custom-key", Metadata.ASCII_STRING_MARSHALLER)
5173
}
5274
}

0 commit comments

Comments
 (0)