ShiinaKin
08/20/2025, 1:08 PMdata class NetworkError(
val type: NetworkErrorType,
val originalException: Throwable? = null
)
class NetworkHandleConfig {
lateinit var onNetworkError: ((NetworkError) -> Unit)
}
val NetworkHandle = createClientPlugin("NetworkHandle", createConfiguration = ::NetworkHandleConfig) {
val onNetworkError = pluginConfig.onNetworkError
on(Send) { context ->
try {
proceed(context)
} catch (e: CancellationException) {
throw e
} catch (e: HttpRequestTimeoutException) {
val networkError = NetworkError(
type = NetworkErrorType.TIMEOUT_ERROR,
originalException = e
)
onNetworkError(networkError)
context.executionContext.cancel(CancellationException("Request cancelled due to timeout"))
proceed(context)
} catch (e: Exception) {
val networkError = when {
e.message?.contains("offline", ignoreCase = true) == true ||
e.message?.contains("connection", ignoreCase = true) == true ||
e.message?.contains("unreachable", ignoreCase = true) == true -> {
NetworkError(
type = NetworkErrorType.CONNECTION_ERROR,
originalException = e
)
}
e.message?.contains("timeout", ignoreCase = true) == true -> {
NetworkError(
type = NetworkErrorType.TIMEOUT_ERROR,
originalException = e
)
}
e.message?.contains("dns", ignoreCase = true) == true ||
e.message?.contains("host", ignoreCase = true) == true -> {
NetworkError(
type = NetworkErrorType.DNS_ERROR,
originalException = e
)
}
e.message?.contains("ssl", ignoreCase = true) == true ||
e.message?.contains("tls", ignoreCase = true) == true ||
e.message?.contains("certificate", ignoreCase = true) == true -> {
NetworkError(
type = NetworkErrorType.SSL_ERROR,
originalException = e
)
}
else -> {
NetworkError(
type = NetworkErrorType.UNKNOWN_ERROR,
originalException = e
)
}
}
onNetworkError(networkError)
context.executionContext.cancel(CancellationException("Request cancelled due to network error"))
proceed(context)
}
}
onRequest { request, _ ->
request.executionContext.invokeOnCompletion { cause ->
if (cause?.cause is HttpRequestTimeoutException) {
val networkError = NetworkError(
type = NetworkErrorType.TIMEOUT_ERROR,
originalException = cause.cause
)
onNetworkError(networkError)
}
}
}
}
install(HttpTimeout) {
requestTimeoutMillis = 3000
}
Aleksei Tirman [JB]
08/20/2025, 1:35 PMHttpTimeout
plugin cancels the execution context when the request timeout occurs, you can add a handler that will be invoked on the job completion. Here is an example:
val client = HttpClient(CIO) {
install(HttpTimeout) {
requestTimeoutMillis = 1000
}
}
client.plugin(HttpSend).intercept { request ->
request.executionContext.invokeOnCompletion { cause ->
if (cause is HttpRequestTimeoutException || cause?.cause is HttpRequestTimeoutException) {
println("Request timeout")
}
}
execute(request)
}
client.get("<https://httpbin.org/delay/5>")
ShiinaKin
08/20/2025, 1:58 PMHttpClient {
if (isDebug) install(Logging)
install(ContentNegotiation) {
json(
Json {
prettyPrint = true
ignoreUnknownKeys = true
isLenient = true
},
)
}
install(HttpTimeout) {
requestTimeoutMillis = 3000
}
install(NetworkHandle) {
onNetworkError = handleNetworkErrorFunc
}
install(Unauthorized) {
handleUnauthorized = handleUnauthorizedFunc
}
.also {
it.plugin(HttpSend).intercept { request ->
println("=====================Intercepting HTTP Request=====================")
request.executionContext.invokeOnCompletion { cause ->
println(">== Request Completed with Cause: ${cause?.message} ==<")
if (cause is HttpRequestTimeoutException || cause?.cause is HttpRequestTimeoutException) {
println(">== Catch HTTP Request Timeout Exception ==<")
request.executionContext.cancel(CancellationException("Request cancelled due to timeout"))
}
}
execute(request)
}
}
.also { isInitialized = true }
log:
=====================Intercepting HTTP Request=====================
HttpClient: REQUEST: <https://aa.aaaa>
METHOD: POST
COMMON HEADERS
-> Accept: application/json
-> Accept-Charset: UTF-8
-> Authorization: Bearer aaaa
CONTENT HEADERS
-> Content-Length: 25
-> Content-Type: application/json
HttpClient: REQUEST <https://aa.aaaa> failed with exception: kotlin.coroutines.cancellation.CancellationException: Request timeout has expired [url=aa.aaaa, request_timeout=3000 ms]
Exception doesn't match @Throws-specified class list and thus isn't
---
=====================Intercepting HTTP Request=====================
HttpClient: REQUEST: <https://aa.aaaa>
METHOD: POST
COMMON HEADERS
-> Accept: application/json
-> Accept-Charset: UTF-8
-> Authorization: Bearer aaaa
CONTENT HEADERS
-> Content-Length: 25
-> Content-Type: application/json
HttpClient: RESPONSE: 200 OK
METHOD: POST
FROM: <https://aa.aaaa>
COMMON HEADERS
-> Content-Type: application/json; charset=UTF-8
-> Date: Wed, 20 Aug 2025 13:51:57 GMT
-> Server: nginx
-> Via: 1.1 <http://aaa.cloudfront.net|aaa.cloudfront.net> (CloudFront)
-> access-control-allow-credentials: false
-> access-control-expose-headers: Content-Type, Content-Length, Authorization, Accept, X-Requested-With
-> x-amz-cf-id: aaa==
-> x-amz-cf-pop: HKG62-P1
-> x-cache: Miss from cloudfront
-> x-powered-by: PHP/7.4.33
=== Request Completed with Cause: null ===
tks for reply
above is the failed log
seems like that before enter the invokeOnCompletion
block, the coroutine was be canceled, and i can't catch it in this blockAleksei Tirman [JB]
08/21/2025, 8:37 AMShiinaKin
08/21/2025, 8:49 AMcontext.executionContext.cancel(CancellationException("Request cancelled due to network error"))
proceed(context)
anyway thanks for help, good day