Skip to content

Commit f86e697

Browse files
committed
Parse trailers even when an error occurs
It is possible for gRPC error responses to be empty and for servers to send the error information within trailers without also sending them in headers. In these cases, Wire will propagate an exception due to no response body and will not attempt to read trailers, relying only on headers instead. This patch allows Wire to attempt to parse the trailers for non-streaming requests instead of only relying on headers.
1 parent 5246ce0 commit f86e697

File tree

3 files changed

+38
-4
lines changed

3 files changed

+38
-4
lines changed

wire-grpc-client/src/jvmMain/kotlin/com/squareup/wire/internal/RealGrpcCall.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ internal class RealGrpcCall<S : Any, R : Any>(
106106
val result = try {
107107
reader.readExactlyOneAndClose()
108108
} catch (e: IOException) {
109-
throw grpcResponseToException(e)!!
109+
throw grpcResponseToException(true, e)!!
110110
}
111111
val exception = grpcResponseToException()
112112
if (exception != null) throw exception

wire-grpc-client/src/jvmMain/kotlin/com/squareup/wire/internal/grpc.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ internal fun <R : Any> SendChannel<R>.readFromResponseBodyCallback(
118118
}
119119
exception = response.grpcResponseToException()
120120
} catch (e: IOException) {
121-
exception = response.grpcResponseToException(e)
121+
exception = response.grpcResponseToException(false, e)
122122
} catch (e: Exception) {
123123
exception = e
124124
} finally {
@@ -200,10 +200,10 @@ private fun Response.checkGrpcResponse() {
200200
}
201201

202202
/** Returns an exception if the gRPC call didn't have a grpc-status of 0. */
203-
internal fun Response.grpcResponseToException(suppressed: IOException? = null): IOException? {
203+
internal fun Response.grpcResponseToException(canReadTrailersOnException: Boolean = false, suppressed: IOException? = null): IOException? {
204204
var trailers = headersOf()
205205
var transportException = suppressed
206-
if (suppressed == null) {
206+
if (suppressed == null || canReadTrailersOnException) {
207207
try {
208208
trailers = trailers()
209209
} catch (e: IOException) {

wire-grpc-tests/src/test/java/com/squareup/wire/GrpcClientTest.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ import kotlinx.coroutines.runBlocking
5555
import kotlinx.coroutines.sync.Mutex
5656
import kotlinx.coroutines.sync.withLock
5757
import okhttp3.Call
58+
import okhttp3.ExperimentalOkHttpApi
59+
import okhttp3.Headers
5860
import okhttp3.Interceptor
5961
import okhttp3.Interceptor.Chain
6062
import okhttp3.MediaType.Companion.toMediaType
@@ -1231,6 +1233,38 @@ class GrpcClientTest {
12311233
}
12321234
}
12331235

1236+
@Test
1237+
fun requestFailureInTrailersNotInHeadersWithEmptyResponseBody() {
1238+
mockService.enqueue(ReceiveCall("/routeguide.RouteGuide/RouteChat"))
1239+
mockService.enqueueReceivePoint(latitude = 5, longitude = 6)
1240+
mockService.enqueue(ReceiveComplete)
1241+
mockService.enqueueSendError(
1242+
Status.UNAUTHENTICATED.withDescription("not logged in")
1243+
.asRuntimeException(),
1244+
)
1245+
mockService.enqueue(SendCompleted)
1246+
1247+
@OptIn(ExperimentalOkHttpApi::class)
1248+
interceptor = object : Interceptor {
1249+
override fun intercept(chain: Chain): Response {
1250+
val response = chain.proceed(chain.request())
1251+
return response.newBuilder()
1252+
.headers(Headers.headersOf())
1253+
.trailers { response.headers }
1254+
.build()
1255+
}
1256+
}
1257+
1258+
val grpcCall = routeGuideService.GetFeature()
1259+
try {
1260+
grpcCall.executeBlocking(Point(latitude = 5, longitude = 6))
1261+
fail()
1262+
} catch (expected: GrpcException) {
1263+
assertThat(expected.grpcStatus).isEqualTo(GrpcStatus.UNAUTHENTICATED)
1264+
assertThat(expected.grpcMessage).isEqualTo("not logged in")
1265+
}
1266+
}
1267+
12341268
@Test
12351269
fun requestEarlyFailureWithDescription() {
12361270
mockService.enqueue(ReceiveCall("/routeguide.RouteGuide/GetFeature"))

0 commit comments

Comments
 (0)