Skip to content

Commit

Permalink
KTOR-721 Provide way to customize unhandled responses (#3278)
Browse files Browse the repository at this point in the history
  • Loading branch information
rsinukov committed Nov 30, 2022
1 parent 4279241 commit ee4ca55
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 0 deletions.
Expand Up @@ -5,6 +5,7 @@ public final class io/ktor/server/plugins/statuspages/StatusPagesConfig {
public final fun getStatuses ()Ljava/util/Map;
public final fun status ([Lio/ktor/http/HttpStatusCode;Lkotlin/jvm/functions/Function3;)V
public final fun statusWithContext ([Lio/ktor/http/HttpStatusCode;Lkotlin/jvm/functions/Function3;)V
public final fun unhandled (Lkotlin/jvm/functions/Function2;)V
}

public final class io/ktor/server/plugins/statuspages/StatusPagesConfig$StatusContext {
Expand Down
Expand Up @@ -34,6 +34,7 @@ public val StatusPages: ApplicationPlugin<StatusPagesConfig> = createApplication

val exceptions = HashMap(pluginConfig.exceptions)
val statuses = HashMap(pluginConfig.statuses)
val unhandled = pluginConfig.unhandled

fun findHandlerByValue(cause: Throwable): HandlerFunction? {
val keys = exceptions.keys.filter { cause.instanceOf(it) }
Expand Down Expand Up @@ -92,6 +93,11 @@ public val StatusPages: ApplicationPlugin<StatusPagesConfig> = createApplication
handler(call, cause)
}
}

on(BeforeFallback) { call ->
if (call.isHandled) return@on
unhandled(call)
}
}

/**
Expand All @@ -111,6 +117,8 @@ public class StatusPagesConfig {
suspend (call: ApplicationCall, content: OutgoingContent, code: HttpStatusCode) -> Unit> =
mutableMapOf()

internal var unhandled: suspend (ApplicationCall) -> Unit = {}

/**
* Register an exception [handler] for the exception type [T] and its children.
*/
Expand Down Expand Up @@ -143,6 +151,13 @@ public class StatusPagesConfig {
}
}

/**
* Register a [handler] for the unhandled calls.
*/
public fun unhandled(handler: suspend (ApplicationCall) -> Unit) {
unhandled = handler
}

/**
* Register a status [handler] for the [status] code.
*/
Expand Down
Expand Up @@ -4,6 +4,16 @@

package io.ktor.server.plugins.statuspages

import io.ktor.server.application.*
import io.ktor.util.pipeline.*
import kotlin.reflect.*

internal expect fun selectNearestParentClass(cause: Throwable, keys: List<KClass<*>>): KClass<*>?

internal object BeforeFallback : Hook<suspend (ApplicationCall) -> Unit> {
override fun install(pipeline: ApplicationCallPipeline, handler: suspend (ApplicationCall) -> Unit) {
val phase = PipelinePhase("BeforeFallback")
pipeline.insertPhaseBefore(ApplicationCallPipeline.Fallback, phase)
pipeline.intercept(phase) { handler(context) }
}
}
Expand Up @@ -540,4 +540,31 @@ class StatusPagesTest {
assertEquals(HttpStatusCode.InternalServerError, response.status)
assertEquals("Handled", response.bodyAsText())
}

@Test
fun testUnhandled() = testApplication {
install(StatusPages) {
unhandled { call -> call.respond(HttpStatusCode.InternalServerError, "body") }
}
routing {
get("route") {
call.response.headers.append("Custom-Header", "Custom-Value")
}
get("handled") {
call.respond("OK")
}
}

val responseHandled = client.get("handled")
assertEquals("OK", responseHandled.bodyAsText())

val responseNoRoute = client.get("/no-route")
assertEquals(HttpStatusCode.InternalServerError, responseNoRoute.status)
assertEquals("body", responseNoRoute.bodyAsText())

val responseWithRoute = client.get("/route")
assertEquals(HttpStatusCode.InternalServerError, responseWithRoute.status)
assertEquals("Custom-Value", responseWithRoute.headers["Custom-Header"])
assertEquals("body", responseWithRoute.bodyAsText())
}
}

0 comments on commit ee4ca55

Please sign in to comment.