Skip to content

Commit

Permalink
server: notFoundHandler is now decorated separately in each context, …
Browse files Browse the repository at this point in the history
…so that decorators can intercept and handle missing routes (e.g. CorsHandler)
  • Loading branch information
angryziber committed Aug 23, 2023
1 parent 67b4e4a commit 86d52cd
Show file tree
Hide file tree
Showing 3 changed files with 11 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Unreleased
* server: notFoundHandler is now decorated separately in each context, so that decorators can intercept and handle missing routes (e.g. CorsHandler)
* server: ErrorHandler will now omit the default message thrown by first() function ("List/Collection is empty.") and generate a standard 404 NotFound response
* server: ThrowableHandler now has HttpExchange as receiver for cleaner code, as it is not used in most handlers anyway
* server: ThrowableHandler returning null will now proceed with next handler, eventually producing error 500 if not handled
Expand Down
2 changes: 1 addition & 1 deletion server/src/klite/CorsHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import kotlin.time.Duration.Companion.days

/**
* An easy way to enable CORS (cross-origin requests).
* Currently, must be registered before Server.notFoundHandler, e.g. provided via the Registry.
* Enable with `before<CorsHandler>()`
*/
open class CorsHandler(
val maxAge: Duration = 7.days,
Expand Down
15 changes: 9 additions & 6 deletions server/src/klite/Server.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Server(
val errors: ErrorHandler = registry.require(),
decorators: List<Decorator> = registry.requireAllDecorators(),
private val sessionStore: SessionStore? = registry.optional(),
val notFoundHandler: Handler = decorators.wrap { ErrorResponse(NotFound, path) },
val notFoundHandler: Handler = { ErrorResponse(NotFound, path) },
override val pathParamRegexer: PathParamRegexer = registry.require(),
private val httpExchangeCreator: KFunction<HttpExchange> = HttpExchange::class.primaryConstructor!!,
): RouterConfig(decorators, registry.requireAll(), registry.requireAll()), MutableRegistry by registry {
Expand Down Expand Up @@ -68,16 +68,19 @@ class Server(
}
}

/** Adds a new router context. When handing a request, the longest matching router context is chosen. */
/** Adds a new router context. When handing a request, the longest matching router context is chosen */
fun context(prefix: String, block: Router.() -> Unit = {}) =
Router(prefix, registry, pathParamRegexer, decorators, renderers, parsers).also { router ->
addContext(prefix, router) { runHandler(this, router.route(this)) }
var notFoundHandler = notFoundHandler
addContext(prefix, router) { runHandler(this, router.route(this), notFoundHandler) }
router.block()
notFoundHandler = router.decorators.wrap { notFoundHandler }
}

fun assets(prefix: String, handler: AssetsHandler) {
val route = Route(GET, prefix.toRegex(), handler::class.annotations, handler).apply { decoratedHandler = decorators.wrap(handler) }
addContext(prefix, this, Dispatchers.IO) { runHandler(this, route.takeIf { method == GET || method == HEAD }) }
val notFoundHandler = decorators.wrap(notFoundHandler)
addContext(prefix, this, Dispatchers.IO) { runHandler(this, route.takeIf { method == GET || method == HEAD }, notFoundHandler) }
}

private fun addContext(prefix: String, config: RouterConfig, extraCoroutineContext: CoroutineContext = EmptyCoroutineContext, handler: Handler) {
Expand All @@ -89,10 +92,10 @@ class Server(
}
}

private suspend fun runHandler(exchange: HttpExchange, route: Route?) {
private suspend fun runHandler(exchange: HttpExchange, route: Route?, wrappedNotFoundHandler: Handler) {
try {
if (route != null) exchange.route = route
val result = (route?.decoratedHandler ?: notFoundHandler).invoke(exchange)
val result = (route?.decoratedHandler ?: wrappedNotFoundHandler).invoke(exchange)
if (!exchange.isResponseStarted) exchange.handle(result)
else if (result != null && result != Unit) logger.warn("Response already started, cannot render $result")
} catch (ignore: BodyNotAllowedException) {
Expand Down

0 comments on commit 86d52cd

Please sign in to comment.