Can pipeline interceptors handle exceptions thrown...
# ktor
s
Can pipeline interceptors handle exceptions thrown further down in the pipeline? I’m trying to add a simple interceptor that retries a call (with a delay) if the call fails. I’d like this to supplement the existing timeout/validation features.
r
Client or server?
s
Client.
@rharter Not sure if you had any thoughts? It looks like exceptions that are thrown from an interceptor just stop the pipeline entirely which is kind of undesirable.
r
Nope, sorry. I use the server but haven’t really used the client, so can’t offer much assistance.
I was going to suggest checking out the StatusPages feature, since it’s basically a glorified exception handler, but then I realized you may have been talking about the client.
s
Honestly the code is probably similar between each. I’ll take a look.
I do wish there was a better description of the pipeline lifecycle on the docs.
@e5l Any insight on this topic?
e
Sure. You can wrap
proceed
or
proceedWith
method with
try catch
and it will give you the exception!
s
I had thought that would work, but this fails (maybe I’m misunderstanding the lifecycle here?):
Copy code
override fun install(feature: RetryOnFailure, scope: HttpClient) {
            scope.receivePipeline.intercept(HttpReceivePipeline.Before) {
                proceedWith(it)
                throw RuntimeException()
            }

            scope.receivePipeline.intercept(HttpReceivePipeline.After) {
                try {
                    proceedWith(it)
                } catch (e: Exception) {
                    println("Hello world")
                    e.printStackTrace()
                }
            }
        }
e
It should run from
Before
to
After
, so the
Before
will receive the exception thrown in
After
s
Ah that’s what I had thought. So proceed literally proceeds down the chain then yields back to earlier stages? Makes sense (if i’m understanding correctly)
e
Exactly.
s
Ok cool. The only small wrinkle is that validation is handled in an ad-hoc
BeforeReceive
phase. Somehow I’ll need to figure out how to insert even before that.
e
The
Pipeline
has special methods to insert phase before and after. So you can create a new phase, insert it, and then intercept.
s
Right, I’ll do that. Sorry for all of the questions 🙂 just one more: Do exceptions span pipelines or are individual pipelines executed separately? I.e. what’s the relationship between
HttpResponsePipeline
and
HttpReceivePipeline
?
e
It depends on the exception, but usually, they are executed separately. We haven't had any usecases for that, and If you have one you could file an issue.
s
The only usecase would be to handle timeouts and validation exceptions in a single interceptor. I’ll need to check exactly which pipeline propagates timeouts as I’m unsure right now.
@e5l I tried several approaches, but all resulted in a
java.lang.ClassCastException: class io.ktor.client.utils.EmptyContent cannot be cast to class io.ktor.client.call.HttpClientCall
when a timeout occurs (via the
Timeout
feature). Here’s the configuration that causes this:
Copy code
val handleValidation = PipelinePhase("HandleValidation")
scope.requestPipeline.insertPhaseBefore(HttpRequestPipeline.Before, handleValidation)
scope.requestPipeline.intercept(handleValidation) {
    repeat(5) {
        try {
            proceed()
        } catch (e: Exception) {
            delay(100)
            println("retry attempt: $it")
        }
    }
}
@e5l Sorry to keep pinging you, but I’m still having trouble with this exception. Any idea why calling
proceed()
multiple times from the interceptor above would cause this?