Server: harden against crashes

This commit is contained in:
2026-09-01 16:31:00 +02:00
parent fce1c097d4
commit 9e010c7873
3 changed files with 27 additions and 9 deletions
@@ -185,7 +185,7 @@ fun Application.module() {
cseAuth(Routes.CSE.monitor, cseAuth(Routes.CSE.monitor,
{ id, user -> transaction { accessibleProjectCSE(user, id, true) } }, { id, user -> transaction { accessibleProjectCSE(user, id, true) } },
ProjectHandler::monitorSetup) { _, _, bus, event -> ProjectHandler::monitorSetup, ProjectHandler::monitorTeardown) { _, _, bus, event ->
try { try {
when(event) { when(event) {
is ClientMonitorEvent.ApplicationFinished -> bus.end(ServerMonitorEvent.ApplicationEnd(Clock.System.now(), event.exitCode)) is ClientMonitorEvent.ApplicationFinished -> bus.end(ServerMonitorEvent.ApplicationEnd(Clock.System.now(), event.exitCode))
@@ -282,6 +282,7 @@ inline fun <reified TParams: Any, reified TEvent: Any, TInter, TExtra> Route.wra
api: CSERoute<TParams, TEvent>, api: CSERoute<TParams, TEvent>,
noinline extra: suspend (ApplicationCall, TParams) -> TInter, noinline extra: suspend (ApplicationCall, TParams) -> TInter,
noinline setup: suspend (TParams, TInter) -> TExtra, noinline setup: suspend (TParams, TInter) -> TExtra,
noinline onClose: suspend (TParams, TInter, TExtra) -> Unit,
noinline handler: suspend (TParams, TInter, TExtra, TEvent) -> Unit noinline handler: suspend (TParams, TInter, TExtra, TEvent) -> Unit
) { ) {
webSocket(api.pattern) { webSocket(api.pattern) {
@@ -292,11 +293,15 @@ inline fun <reified TParams: Any, reified TEvent: Any, TInter, TExtra> Route.wra
val converter = KotlinxWebsocketSerializationConverter(Json) val converter = KotlinxWebsocketSerializationConverter(Json)
val extra = setup(params, inter) val extra = setup(params, inter)
for(frame in incoming) { try {
handler(params, inter, extra, converter.deserialize<TEvent>(frame)) for (frame in incoming) {
handler(params, inter, extra, converter.deserialize<TEvent>(frame))
}
}
catch(e: ClosedReceiveChannelException) {
onClose(params, inter, extra)
} }
} }
catch(e: ClosedReceiveChannelException) {}
catch(e: RouteError.CSERouteError) { catch(e: RouteError.CSERouteError) {
close(CloseReason(e.code, e.message ?: "Unknown error")) close(CloseReason(e.code, e.message ?: "Unknown error"))
} }
@@ -310,6 +315,7 @@ inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.wrapAuthCSE
api: CSERoute<TParams, TEvent>, api: CSERoute<TParams, TEvent>,
noinline verifyUser: suspend (TParams, User) -> Unit, noinline verifyUser: suspend (TParams, User) -> Unit,
noinline setup: suspend (TParams, User) -> TExtra, noinline setup: suspend (TParams, User) -> TExtra,
noinline onClose: suspend (TParams, User, TExtra) -> Unit,
noinline handler: suspend (TParams, User, TExtra, TEvent) -> Unit noinline handler: suspend (TParams, User, TExtra, TEvent) -> Unit
) = wrapCSE<TParams, TEvent, User, TExtra>( ) = wrapCSE<TParams, TEvent, User, TExtra>(
api = api, api = api,
@@ -327,28 +333,32 @@ inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.wrapAuthCSE
user user
}, },
setup = setup, setup = setup,
onClose = onClose,
handler = handler handler = handler
) )
inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cse( inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cse(
api: CSERoute<TParams, TEvent>, api: CSERoute<TParams, TEvent>,
noinline setup: suspend (TParams, Unit) -> TExtra, noinline setup: suspend (TParams) -> TExtra,
noinline onClose: suspend (TParams, TExtra) -> Unit,
noinline handler: suspend (TParams, TExtra, TEvent) -> Unit noinline handler: suspend (TParams, TExtra, TEvent) -> Unit
) { ) {
if(api.elevation != Elevation.UN_AUTH) throw IllegalArgumentException("CSE ${api.pattern} can only be used with ${api.elevation}") if(api.elevation != Elevation.UN_AUTH) throw IllegalArgumentException("CSE ${api.pattern} can only be used with ${api.elevation}")
wrapCSE(api, { _, _ -> }, setup) { params, _, extra, event -> handler(params, extra, event) } wrapCSE(api, { _, _ -> }, { it, _ -> setup(it) }, { p, _, extra -> onClose(p, extra) }) { params, _, extra, event -> handler(params, extra, event) }
} }
inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cseAuth( inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cseAuth(
api: CSERoute<TParams, TEvent>, noinline verifyUser: suspend (TParams, User) -> Unit, api: CSERoute<TParams, TEvent>, noinline verifyUser: suspend (TParams, User) -> Unit,
noinline setup: suspend (TParams, User) -> TExtra, noinline handler: suspend (TParams, User, TExtra, TEvent) -> Unit noinline setup: suspend (TParams, User) -> TExtra, noinline onClose: suspend (TParams, User, TExtra) -> Unit,
noinline handler: suspend (TParams, User, TExtra, TEvent) -> Unit
) { ) {
if(api.elevation != Elevation.AUTH) throw IllegalArgumentException("CSE ${api.pattern} can only be used with ${api.elevation}") if(api.elevation != Elevation.AUTH) throw IllegalArgumentException("CSE ${api.pattern} can only be used with ${api.elevation}")
wrapAuthCSE(api, verifyUser, setup, handler) wrapAuthCSE(api, verifyUser, setup, onClose, handler)
} }
inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cseAdmin( inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cseAdmin(
api: CSERoute<TParams, TEvent>, noinline setup: suspend (TParams, User) -> TExtra, api: CSERoute<TParams, TEvent>, noinline setup: suspend (TParams, User) -> TExtra,
noinline onClose: suspend (TParams, User, TExtra) -> Unit,
noinline handler: suspend (TParams, User, TExtra, TEvent) -> Unit noinline handler: suspend (TParams, User, TExtra, TEvent) -> Unit
) { ) {
if(api.elevation != Elevation.ADMIN) throw IllegalArgumentException("CSE ${api.pattern} can only be used with ${api.elevation}") if(api.elevation != Elevation.ADMIN) throw IllegalArgumentException("CSE ${api.pattern} can only be used with ${api.elevation}")
@@ -356,5 +366,5 @@ inline fun <reified TParams: Any, reified TEvent: Any, TExtra> Route.cseAdmin(
if(!user.isAdmin) { if(!user.isAdmin) {
throw RouteError.CSERouteError("Admin access required", CloseReasons.NOT_AUTHORIZED) throw RouteError.CSERouteError("Admin access required", CloseReasons.NOT_AUTHORIZED)
} }
}, setup, handler) }, setup, onClose, handler)
} }
@@ -402,6 +402,14 @@ object ProjectHandler {
} }
} }
suspend fun monitorTeardown(
id: Uuid, user: User,
bus: SSEBus.MonitorSSEBus<ServerMonitorEvent, ServerMonitorEvent.ApplicationStart, ServerMonitorEvent.Message,
ServerMonitorEvent.ApplicationEnd, ServerMonitorEvent.Backlog>
) {
bus.end(ServerMonitorEvent.ApplicationEnd(Clock.System.now(), -1))
}
fun deleteMonitor(user: User, req: Uuid) = transaction { fun deleteMonitor(user: User, req: Uuid) = transaction {
checkMigration(user) checkMigration(user)
accessibleProject(user, req, true) accessibleProject(user, req, true)