Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ internal class GrpcMessageSource<T : Any>(
}
}

fun isEmptyBody(): Boolean = source.exhausted()

fun readExactlyOneAndClose(): T {
use(GrpcMessageSource<T>::close) { reader ->
val result = reader.read() ?: throw ProtocolException("expected 1 message but got none")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,18 @@ internal class RealGrpcCall<S : Any, R : Any>(
use {
messageSource(method.responseAdapter).use { reader ->
val result = try {
reader.readExactlyOneAndClose()
if (reader.isEmptyBody()) {
// an empty body is valid for error responses. don't throw so we try to parse trailers.
null
} else {
reader.readExactlyOneAndClose()
}
} catch (e: IOException) {
throw grpcResponseToException(e)!!
}
val exception = grpcResponseToException()
if (exception != null) throw exception
return result
return result ?: throw grpcResponseToException(ProtocolException("expected 1 message but got none"))!!
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions wire-grpc-tests/src/test/java/com/squareup/wire/GrpcClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.Call
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.Interceptor.Chain
import okhttp3.MediaType.Companion.toMediaType
Expand All @@ -66,6 +67,7 @@ import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody
import okhttp3.TrailersSource
import okio.Buffer
import okio.ByteString
import okio.ForwardingSource
Expand Down Expand Up @@ -1230,6 +1232,39 @@ class GrpcClientTest {
}
}

@Test
fun requestFailureInTrailersNotInHeadersWithEmptyResponseBody() {
mockService.enqueue(ReceiveCall("/routeguide.RouteGuide/RouteChat"))
mockService.enqueueReceivePoint(latitude = 5, longitude = 6)
mockService.enqueue(ReceiveComplete)
mockService.enqueueSendError(
Status.UNAUTHENTICATED.withDescription("not logged in")
.asRuntimeException(),
)
mockService.enqueue(SendCompleted)

interceptor = Interceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.headers(Headers.headersOf())
.trailers(
object : TrailersSource {
override fun get() = response.headers
},
)
.build()
}

val grpcCall = routeGuideService.GetFeature()
try {
grpcCall.executeBlocking(Point(latitude = 5, longitude = 6))
fail()
} catch (expected: GrpcException) {
assertThat(expected.grpcStatus).isEqualTo(GrpcStatus.UNAUTHENTICATED)
assertThat(expected.grpcMessage).isEqualTo("not logged in")
}
}

@Test
fun requestEarlyFailureWithDescription() {
mockService.enqueue(ReceiveCall("/routeguide.RouteGuide/GetFeature"))
Expand Down