/kotlin-coroutines-okhttp

Kotlin Coroutines await() extension for OkHttp Call

Primary LanguageKotlinApache License 2.0Apache-2.0

Kotlin coroutines await extension for OkHttp3

This is small library that provides await() extension for okhttp3.Call for integration with Kotlin coroutines

Based on kotlinx.coroutines implementation.

Requires Kotlin 1.3+

Depends on OkHttp3 3.8.0 so don't require updates to newest version of OkHttp that require Java 8+ or Android 5+

Usage

// Create OkHttp client
val client = OkHttpClient.Builder().build()
// Create request 
val request = Request.Builder().url("https://example.org/").build()

suspend fun main() {
    // Do call and await() for result from any suspend function
    val result = client.newCall(request).await()
    println("${result.code()}: ${result.message()}")
}

This library doesn't provide any facilities for non-blocking read of response body, if you need a way to do that consider to use withContext(Dispatchers.IO) and wrap blocking calls.

See this issue about support of non-blocking API for Okio - square/okio#501

Download

Download the JAR:

Artifacts are available on JCenter and Maven Central

Gradle:

implementation("ru.gildor.coroutines:kotlin-coroutines-okhttp:1.0")

Maven:

<dependency>
  <groupId>ru.gildor.coroutines</groupId>
  <artifactId>kotlin-coroutines-okhttp</artifactId>
  <version>1.0</version>
</dependency>

Debugging

Because this coroutines adapter uses async API of OkHttp by default stacktrace doesn't contain link on code that called it.

For example you have code from Usage section code that throws SocketTimeoutException

If you run it you will get stacktrace similar to this:

Exception in thread "main" java.net.SocketTimeoutException: Read timed out
	at java.base/java.net.SocketInputStream.socketRead0(Native Method)
	at java.base/java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	...

As you can see, there is no way to understand from which line or class this request was started.

You should understand, this is not something unique for Kotlin coroutines or this adapter, you would have the same stacktrace with okhttp3.Callback if would throw an exception on failure To make this stacktrace more useful you can do a few things

Wrap exception manually:

suspend fun main() {
   try {
       client.newCall(request).await()
   } catch (e: IOException) {
       // Catch original exception
       // Use some logger that will write line number to logs, so you can find the source of exception
       someLogger.error(e)
       // or just wrap exception and throw it to get stacktrace
       throw IOException("Some additional debug info: ${e.message}", e)
   }
}

This will give you stacktrace that shows line where exception was rethrown or logged:

Exception in thread "main" java.io.IOException: java.net.SocketTimeoutException: Read timed out
    at ru.gildor.coroutines.okhttp.MainKt.main(main.kt:20)
    at ru.gildor.coroutines.okhttp.MainKt$main$1.invokeSuspend(main.kt)
...
Caused by: java.net.SocketTimeoutException: Read timed out
    at java.base/java.net.SocketInputStream.socketRead0(Native Method)
    at java.base/java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
...

Enable debug mode of kotlinx.coroutines:

// Set it before first usage of coroutines in your project
System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)

This will allow you to see line where request is started:

Exception in thread "main" java.net.SocketTimeoutException
    (Coroutine boundary)
    at ru.gildor.coroutines.okhttp.MainKt.main(main.kt:15)
Caused by: java.net.SocketTimeoutException: timeout
    at okio.Okio$4.newTimeoutException(Okio.java:230)
    at okio.AsyncTimeout.exit(AsyncTimeout.java:285)
...

This will give you coroutines stack so you can debug it in most cases

Enable stack recording

To help debug some complicated cases, when you want to know which code started coroutine, await() provides argument recordStack:

suspend fun main() {
    client.newCall(request).await(recordStack = true)
}

Instead of recovering coroutine stacktrace this call will create exception on method call to save stack and will throw it on error, so you will get detailed stack

Exception in thread "main" java.io.IOException
	at ru.gildor.coroutines.okhttp.MainKt.main(main.kt:15)
	...
Caused by: java.net.SocketTimeoutException: timeout
	at okio.Okio$4.newTimeoutException(Okio.java:230)
	...

By default stack recording is disabled, but you can enable it using system properties (should set it before first usage of the adapter):

System.setProperty(OKHTTP_STACK_RECORDER_PROPERTY, OKHTTP_STACK_RECORDER_ON)

But this method is relatively heavyweight because creates exception on each request (even successful, because we have to record stacktrace before invocation) and resulting stacktrace is full of coroutines internal calls,

So I recommend to use it only if you have hard times to understand what is caused the problem and you need all information