55 */
66package io.jooby.kt
77
8- import io.jooby.RequestScope
9- import io.jooby.Route
10- import io.jooby.Router
11- import io.jooby.Router.DELETE
12- import io.jooby.Router.GET
13- import io.jooby.Router.HEAD
14- import io.jooby.Router.OPTIONS
15- import io.jooby.Router.PATCH
16- import io.jooby.Router.POST
17- import io.jooby.Router.PUT
18- import io.jooby.Router.TRACE
8+ import io.jooby.*
9+ import io.jooby.Router.*
10+ import java.util.function.Predicate
1911import kotlin.coroutines.CoroutineContext
2012import kotlin.coroutines.EmptyCoroutineContext
21- import kotlinx.coroutines.CoroutineExceptionHandler
22- import kotlinx.coroutines.CoroutineScope
23- import kotlinx.coroutines.CoroutineStart
24- import kotlinx.coroutines.asContextElement
25- import kotlinx.coroutines.asCoroutineDispatcher
26- import kotlinx.coroutines.launch
13+ import kotlin.reflect.KClass
14+ import kotlinx.coroutines.*
2715
2816internal class RouterCoroutineScope (override val coroutineContext : CoroutineContext ) :
2917 CoroutineScope
@@ -34,6 +22,8 @@ class CoroutineRouter(val coroutineStart: CoroutineStart, val router: Router) {
3422 RouterCoroutineScope (router.worker.asCoroutineDispatcher())
3523 }
3624
25+ private var errorHandler: suspend ErrorHandlerContext .() -> Unit = FALLBACK_ERROR_HANDLER
26+
3727 private var extraCoroutineContextProvider: HandlerContext .() -> CoroutineContext = {
3828 EmptyCoroutineContext
3929 }
@@ -42,6 +32,84 @@ class CoroutineRouter(val coroutineStart: CoroutineStart, val router: Router) {
4232 extraCoroutineContextProvider = provider
4333 }
4434
35+ /* *
36+ * Add a custom error handler that matches the given status code.
37+ *
38+ * @param statusCode Status code.
39+ * @param handler Error handler.
40+ * @return This router.
41+ */
42+ @RouterDsl
43+ fun error (
44+ statusCode : StatusCode ,
45+ handler : suspend ErrorHandlerContext .() -> Unit
46+ ): CoroutineRouter {
47+ return error({ it: StatusCode -> statusCode == it }, handler)
48+ }
49+
50+ /* *
51+ * Add a custom error handler that matches the given exception type.
52+ *
53+ * @param type Exception type.
54+ * @param handler Error handler.
55+ * @return This router.
56+ */
57+ @RouterDsl
58+ fun error (
59+ type : KClass <Throwable >,
60+ handler : suspend ErrorHandlerContext .() -> Unit
61+ ): CoroutineRouter {
62+ return error {
63+ if (type.java.isInstance(cause) || type.java.isInstance(cause.cause)) {
64+ handler.invoke(this )
65+ }
66+ }
67+ }
68+
69+ /* *
70+ * Add a custom error handler that matches the given predicate.
71+ *
72+ * @param predicate Status code filter.
73+ * @param handler Error handler.
74+ * @return This router.
75+ */
76+ @RouterDsl
77+ fun error (
78+ predicate : Predicate <StatusCode >,
79+ handler : suspend ErrorHandlerContext .() -> Unit
80+ ): CoroutineRouter {
81+ return error {
82+ if (predicate.test(statusCode)) {
83+ handler.invoke(this )
84+ }
85+ }
86+ }
87+
88+ /* *
89+ * Add a custom error handler.
90+ *
91+ * @param handler Error handler.
92+ * @return This router.
93+ */
94+ @RouterDsl
95+ fun error (handler : suspend ErrorHandlerContext .() -> Unit ): CoroutineRouter {
96+ val chain =
97+ fun (
98+ current : suspend ErrorHandlerContext .() -> Unit ,
99+ next : suspend ErrorHandlerContext .() -> Unit
100+ ): suspend ErrorHandlerContext .() -> Unit {
101+ return {
102+ current(this )
103+ if (! ctx.isResponseStarted) {
104+ next(this )
105+ }
106+ }
107+ }
108+ errorHandler =
109+ if (errorHandler == FALLBACK_ERROR_HANDLER ) handler else chain(errorHandler, handler)
110+ return this
111+ }
112+
45113 @RouterDsl
46114 fun get (pattern : String , handler : suspend HandlerContext .() -> Any ) = route(GET , pattern, handler)
47115
@@ -77,10 +145,18 @@ class CoroutineRouter(val coroutineStart: CoroutineStart, val router: Router) {
77145 .route(method, pattern) { ctx ->
78146 val handlerContext = HandlerContext (ctx)
79147 launch(handlerContext) {
80- val result = handler(handlerContext)
81- ctx.route.after?.apply (ctx, result, null )
82- if (result != ctx && ! ctx.isResponseStarted) {
83- ctx.render(result)
148+ try {
149+ val result = handler(handlerContext)
150+ ctx.route.after?.apply (ctx, result, null )
151+ if (result != ctx && ! ctx.isResponseStarted) {
152+ ctx.render(result)
153+ }
154+ } catch (cause: Throwable ) {
155+ try {
156+ ctx.route.after?.apply (ctx, null , cause)
157+ } finally {
158+ errorHandler.invoke(ErrorHandlerContext (ctx, cause, router.errorCode(cause)))
159+ }
84160 }
85161 }
86162 // Return context to mark as handled
@@ -89,6 +165,7 @@ class CoroutineRouter(val coroutineStart: CoroutineStart, val router: Router) {
89165 .setHandle(handler)
90166
91167 internal fun launch (handlerContext : HandlerContext , block : suspend CoroutineScope .() -> Unit ) {
168+ // Global catch-all exception handler
92169 val exceptionHandler = CoroutineExceptionHandler { _, x ->
93170 val ctx = handlerContext.ctx
94171 ctx.route.after?.apply (ctx, null , x)
@@ -99,4 +176,10 @@ class CoroutineRouter(val coroutineStart: CoroutineStart, val router: Router) {
99176 exceptionHandler + requestScope + handlerContext.extraCoroutineContextProvider()
100177 coroutineScope.launch(coroutineContext, coroutineStart, block)
101178 }
179+
180+ private companion object {
181+ private val FALLBACK_ERROR_HANDLER : suspend ErrorHandlerContext .() -> Unit = {
182+ ctx.sendError(cause, statusCode)
183+ }
184+ }
102185}
0 commit comments