diff --git a/authorization/src/main/kotlin/org/modelix/authorization/KtorAuthUtils.kt b/authorization/src/main/kotlin/org/modelix/authorization/KtorAuthUtils.kt index 1f079b3c6d..589654186d 100644 --- a/authorization/src/main/kotlin/org/modelix/authorization/KtorAuthUtils.kt +++ b/authorization/src/main/kotlin/org/modelix/authorization/KtorAuthUtils.kt @@ -15,7 +15,7 @@ import io.ktor.server.auth.parseAuthorizationHeader import io.ktor.server.auth.principal import io.ktor.server.request.header import io.ktor.server.routing.Route -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext import org.modelix.authorization.permissions.PermissionEvaluator import org.modelix.authorization.permissions.PermissionInstanceReference import org.modelix.authorization.permissions.PermissionParts @@ -35,7 +35,7 @@ fun Route.requiresLogin(body: Route.() -> Unit) { } } -fun PipelineContext<*, ApplicationCall>.checkPermission(permissionParts: PermissionParts) { +fun RoutingContext.checkPermission(permissionParts: PermissionParts) { call.checkPermission(permissionParts) } @@ -83,7 +83,7 @@ fun ApplicationCall.jwtFromHeaders(): DecodedJWT? { fun ApplicationCall.jwt() = principal()?.jwt ?: jwtFromHeaders() -fun PipelineContext.getUserName(): String? { +fun RoutingContext.getUserName(): String? { return call.getUserName() } diff --git a/authorization/src/main/kotlin/org/modelix/authorization/PermissionManagementPage.kt b/authorization/src/main/kotlin/org/modelix/authorization/PermissionManagementPage.kt index 005577db69..7e85668efb 100644 --- a/authorization/src/main/kotlin/org/modelix/authorization/PermissionManagementPage.kt +++ b/authorization/src/main/kotlin/org/modelix/authorization/PermissionManagementPage.kt @@ -11,10 +11,11 @@ import io.ktor.server.request.receiveParameters import io.ktor.server.response.respond import io.ktor.server.response.respondRedirect import io.ktor.server.routing.Route +import io.ktor.server.routing.RoutingContext +import io.ktor.server.routing.application import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.route -import io.ktor.util.pipeline.PipelineContext import kotlinx.html.FlowContent import kotlinx.html.a import kotlinx.html.body @@ -83,7 +84,7 @@ fun Route.installPermissionManagementHandlers() { } } route("{resourceId}") { - fun PipelineContext<*, ApplicationCall>.resourceId(): String = call.parameters["resourceId"]!! + fun RoutingContext.resourceId(): String = call.parameters["resourceId"]!! get("/") { val resourceId = resourceId() val plugin = call.application.plugin(ModelixAuthorization) @@ -240,8 +241,8 @@ fun Route.installPermissionManagementHandlers() { } route("permissions") { route("{permissionName}") { - fun PipelineContext<*, ApplicationCall>.permissionName(): String = call.parameters["permissionName"]!! - fun PipelineContext<*, ApplicationCall>.permissionId(): String = "${resourceId()}/${permissionName()}" + fun RoutingContext.permissionName(): String = call.parameters["permissionName"]!! + fun RoutingContext.permissionId(): String = "${resourceId()}/${permissionName()}" post("grant") { val formParameters = call.receiveParameters() grant(formParameters["userId"], formParameters["roleId"], permissionId()) @@ -300,34 +301,34 @@ fun FlowContent.buildPermissionIncludesList(currentResource: ResourceInstanceRef } } -private fun PipelineContext<*, ApplicationCall>.grant(userId: String?, roleId: String?, permissionId: String) { +private fun RoutingContext.grant(userId: String?, roleId: String?, permissionId: String) { val userId = userId?.takeIf { it.isNotBlank() } val roleId = roleId?.takeIf { it.isNotBlank() } require(userId != null || roleId != null) { "userId or roleId required" } call.checkCanGranPermission(permissionId) if (userId != null) { - application.plugin(ModelixAuthorization).config.accessControlPersistence.update { + call.application.plugin(ModelixAuthorization).config.accessControlPersistence.update { it.withGrantToUser(userId, permissionId) } } if (roleId != null) { - application.plugin(ModelixAuthorization).config.accessControlPersistence.update { + call.application.plugin(ModelixAuthorization).config.accessControlPersistence.update { it.withGrantToRole(roleId, permissionId) } } } -private fun PipelineContext<*, ApplicationCall>.removeGrant(userId: String?, roleId: String?, permissionId: String) { +private fun RoutingContext.removeGrant(userId: String?, roleId: String?, permissionId: String) { require(userId != null || roleId != null) { "userId or roleId required" } call.checkCanGranPermission(permissionId) if (userId != null) { - application.plugin(ModelixAuthorization).config.accessControlPersistence.update { + call.application.plugin(ModelixAuthorization).config.accessControlPersistence.update { it.withoutGrantToUser(userId, permissionId) } } if (roleId != null) { - application.plugin(ModelixAuthorization).config.accessControlPersistence.update { + call.application.plugin(ModelixAuthorization).config.accessControlPersistence.update { it.withoutGrantToUser(roleId, permissionId) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6698719ebb..b31ffdcaea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ docker-compose = { id = "com.avast.gradle.docker-compose" , version = "0.17.12" [versions] kotlin = "2.1.0" kotlinCoroutines="1.9.0" -ktor="2.3.12" +ktor="3.0.3" kotlinHtml="0.8.0" kotlinSerialization="1.7.3" ignite="2.16.0" diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index d9a3c2e932..e96738b8d7 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -60,13 +60,6 @@ abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -abort-controller@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - acorn-globals@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" @@ -336,11 +329,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -650,13 +638,6 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -862,11 +843,6 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - tslib@^1.11.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -914,11 +890,6 @@ w3c-xmlserializer@^3.0.0: dependencies: xml-name-validator "^4.0.0" -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -944,14 +915,6 @@ whatwg-url@^11.0.0: tr46 "^3.0.0" webidl-conversions "^7.0.0" -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -976,12 +939,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -ws@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== - -ws@^8.17.1: +ws@8.18.0, ws@^8.17.1: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== diff --git a/model-client-js-test/replicated-model-test/src/ReplicatedModelJS.test.ts b/model-client-js-test/replicated-model-test/src/ReplicatedModelJS.test.ts index 99f77fbf38..ba34f7bb3d 100644 --- a/model-client-js-test/replicated-model-test/src/ReplicatedModelJS.test.ts +++ b/model-client-js-test/replicated-model-test/src/ReplicatedModelJS.test.ts @@ -10,6 +10,8 @@ type ReplicatedModelJS = org.modelix.model.client2.ReplicatedModelJS let client: ClientJS | undefined; let replicatedModel: ReplicatedModelJS | undefined; +jest.setTimeout(60000) + beforeEach(async () => { const repositoryId = randomUUID() client = await connectClient(MODEL_SERVER_URL) @@ -33,7 +35,7 @@ test("replicated model uses user set in client", async () => { const versionInformation = await replicatedModel!.getCurrentVersionInformation() const lastAuthor = versionInformation.author expect(lastAuthor).toBe(userId); -}); +}, ); test("replicated model returns user set in client", async () => { const versionInformation = await replicatedModel!.getCurrentVersionInformation() diff --git a/model-client/src/jvmMain/kotlin/org/modelix/model/client/RestWebModelClient.kt b/model-client/src/jvmMain/kotlin/org/modelix/model/client/RestWebModelClient.kt index a2f5d5839f..9b30ae16bf 100644 --- a/model-client/src/jvmMain/kotlin/org/modelix/model/client/RestWebModelClient.kt +++ b/model-client/src/jvmMain/kotlin/org/modelix/model/client/RestWebModelClient.kt @@ -27,6 +27,7 @@ import io.ktor.util.reflect.TypeInfo import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.charsets.Charset import io.ktor.utils.io.core.readText +import io.ktor.utils.io.readRemaining import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -53,25 +54,6 @@ import java.net.URLEncoder import java.nio.charset.StandardCharsets import java.util.LinkedList import java.util.concurrent.atomic.AtomicInteger -import kotlin.collections.ArrayList -import kotlin.collections.HashMap -import kotlin.collections.Iterable -import kotlin.collections.LinkedHashMap -import kotlin.collections.List -import kotlin.collections.Map -import kotlin.collections.MutableList -import kotlin.collections.MutableMap -import kotlin.collections.Set -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.emptyList -import kotlin.collections.emptySet -import kotlin.collections.filter -import kotlin.collections.forEach -import kotlin.collections.iterator -import kotlin.collections.minus -import kotlin.collections.plus -import kotlin.collections.set import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds @@ -157,8 +139,8 @@ class RestWebModelClient @JvmOverloads constructor( contentType: ContentType, charset: Charset, typeInfo: TypeInfo, - value: Any, - ): OutgoingContent { + value: Any?, + ): OutgoingContent? { return TextContent(value.toString(), contentType) } }, diff --git a/model-server-api/src/commonTest/kotlin/org/modelix/model/server/api/v2/VersionDeltaStreamV2Test.kt b/model-server-api/src/commonTest/kotlin/org/modelix/model/server/api/v2/VersionDeltaStreamV2Test.kt index b7476225fe..a530f92d4a 100644 --- a/model-server-api/src/commonTest/kotlin/org/modelix/model/server/api/v2/VersionDeltaStreamV2Test.kt +++ b/model-server-api/src/commonTest/kotlin/org/modelix/model/server/api/v2/VersionDeltaStreamV2Test.kt @@ -1,8 +1,8 @@ package org.modelix.model.server.api.v2 import io.ktor.util.cio.use -import io.ktor.util.toByteArray import io.ktor.utils.io.ByteChannel +import io.ktor.utils.io.toByteArray import io.ktor.utils.io.writeFully import io.ktor.utils.io.writeStringUtf8 import kotlinx.coroutines.flow.emptyFlow diff --git a/model-server/src/main/kotlin/org/modelix/model/server/Main.kt b/model-server/src/main/kotlin/org/modelix/model/server/Main.kt index 673c857289..b6fa31e4b9 100644 --- a/model-server/src/main/kotlin/org/modelix/model/server/Main.kt +++ b/model-server/src/main/kotlin/org/modelix/model/server/Main.kt @@ -8,14 +8,13 @@ import io.ktor.http.HttpStatusCode import io.ktor.serialization.kotlinx.json.json import io.ktor.server.application.Application import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call import io.ktor.server.application.install +import io.ktor.server.engine.connector import io.ktor.server.engine.embeddedServer import io.ktor.server.http.content.staticResources import io.ktor.server.netty.Netty -import io.ktor.server.netty.NettyApplicationEngine -import io.ktor.server.plugins.callloging.CallLogging -import io.ktor.server.plugins.callloging.processingTimeMillis +import io.ktor.server.plugins.calllogging.CallLogging +import io.ktor.server.plugins.calllogging.processingTimeMillis import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.plugins.cors.routing.CORS import io.ktor.server.plugins.forwardedheaders.ForwardedHeaders @@ -27,7 +26,6 @@ import io.ktor.server.request.path import io.ktor.server.resources.Resources import io.ktor.server.response.respondText import io.ktor.server.routing.IgnoreTrailingSlash -import io.ktor.server.routing.Routing import io.ktor.server.routing.get import io.ktor.server.routing.routing import io.ktor.server.websocket.WebSockets @@ -69,9 +67,9 @@ import java.io.File import java.io.FileReader import java.io.IOException import java.nio.charset.StandardCharsets -import java.time.Duration import java.util.Properties import javax.sql.DataSource +import kotlin.time.Duration.Companion.seconds object Main { private val LOG = LoggerFactory.getLogger(Main::class.java) @@ -173,12 +171,10 @@ object Main { val modelReplicationServer = ModelReplicationServer(repositoriesManager) val metricsApi = MetricsApiImpl() - val configureNetty: NettyApplicationEngine.Configuration.() -> Unit = { - this.responseWriteTimeoutSeconds = cmdLineArgs.responseWriteTimeoutSeconds - } - - val ktorServer: NettyApplicationEngine = embeddedServer(Netty, port = port, configure = configureNetty) { - install(Routing) + val ktorServer = embeddedServer(Netty, configure = { + connector { this.port = port } + responseWriteTimeoutSeconds = cmdLineArgs.responseWriteTimeoutSeconds + }) { install(ModelixAuthorization) { permissionSchema = ModelServerPermissionSchema.SCHEMA installStatusPages = false @@ -205,8 +201,8 @@ object Main { // https://opensource.zalando.com/restful-api-guidelines/#136 install(IgnoreTrailingSlash) install(WebSockets) { - pingPeriod = Duration.ofSeconds(30) - timeout = Duration.ofSeconds(30) + pingPeriod = 30.seconds + timeout = 30.seconds maxFrameSize = Long.MAX_VALUE masking = false } diff --git a/model-server/src/main/kotlin/org/modelix/model/server/handlers/AboutApiImpl.kt b/model-server/src/main/kotlin/org/modelix/model/server/handlers/AboutApiImpl.kt index 8f89137182..a40e6d8281 100644 --- a/model-server/src/main/kotlin/org/modelix/model/server/handlers/AboutApiImpl.kt +++ b/model-server/src/main/kotlin/org/modelix/model/server/handlers/AboutApiImpl.kt @@ -1,16 +1,14 @@ package org.modelix.model.server.handlers -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call import io.ktor.server.response.respond -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext import org.modelix.model.server.MODELIX_VERSION /** * Responding information about the model server. */ object AboutApiImpl : AboutApi() { - override suspend fun PipelineContext.getAboutInformationV1() { + override suspend fun RoutingContext.getAboutInformationV1() { val about = AboutV1(MODELIX_VERSION) call.respond(about) } diff --git a/model-server/src/main/kotlin/org/modelix/model/server/handlers/HealthApiImpl.kt b/model-server/src/main/kotlin/org/modelix/model/server/handlers/HealthApiImpl.kt index d988fab50a..a319e12ab0 100644 --- a/model-server/src/main/kotlin/org/modelix/model/server/handlers/HealthApiImpl.kt +++ b/model-server/src/main/kotlin/org/modelix/model/server/handlers/HealthApiImpl.kt @@ -2,10 +2,8 @@ package org.modelix.model.server.handlers import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call import io.ktor.server.response.respondText -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext import org.modelix.model.server.handlers.KeyValueLikeModelServer.Companion.PROTECTED_PREFIX import org.modelix.model.server.store.RequiresTransaction import org.modelix.model.server.store.StoreManager @@ -16,7 +14,7 @@ class HealthApiImpl( private val stores: StoreManager get() = repositoriesManager.stores - override suspend fun PipelineContext.getHealth() { + override suspend fun RoutingContext.getHealth() { if (isHealthy()) { call.respondText(text = "healthy", contentType = ContentType.Text.Plain, status = HttpStatusCode.OK) } else { diff --git a/model-server/src/main/kotlin/org/modelix/model/server/handlers/IdsApiImpl.kt b/model-server/src/main/kotlin/org/modelix/model/server/handlers/IdsApiImpl.kt index 27c1eee672..1bafa773ff 100644 --- a/model-server/src/main/kotlin/org/modelix/model/server/handlers/IdsApiImpl.kt +++ b/model-server/src/main/kotlin/org/modelix/model/server/handlers/IdsApiImpl.kt @@ -1,12 +1,10 @@ package org.modelix.model.server.handlers import io.ktor.server.application.Application -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call import io.ktor.server.plugins.origin import io.ktor.server.response.respondText +import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.routing -import io.ktor.util.pipeline.PipelineContext import org.modelix.authorization.getUserName import org.modelix.authorization.requiresLogin import org.modelix.model.server.store.RequiresTransaction @@ -19,7 +17,7 @@ class IdsApiImpl( private val repositoriesManager: IRepositoriesManager, ) : IdsApi() { - override suspend fun PipelineContext.getServerId() { + override suspend fun RoutingContext.getServerId() { // Currently, the server ID is initialized in KeyValueLikeModelServer eagerly on startup. // Should KeyValueLikeModelServer be removed or change, // RepositoriesManager#maybeInitAndGetSeverId will initialize the server ID lazily on the first request. @@ -34,11 +32,11 @@ class IdsApiImpl( call.respondText(serverId) } - override suspend fun PipelineContext.getUserId() { + override suspend fun RoutingContext.getUserId() { call.respondText(call.getUserName() ?: call.request.origin.remoteHost) } - override suspend fun PipelineContext.generateClientId() { + override suspend fun RoutingContext.generateClientId() { call.respondText(repositoriesManager.getStoreManager().getGlobalStoreClient().generateId("clientId").toString()) } diff --git a/model-server/src/main/kotlin/org/modelix/model/server/handlers/KeyValueLikeModelServer.kt b/model-server/src/main/kotlin/org/modelix/model/server/handlers/KeyValueLikeModelServer.kt index 3cecbdd4b4..56850b360f 100644 --- a/model-server/src/main/kotlin/org/modelix/model/server/handlers/KeyValueLikeModelServer.kt +++ b/model-server/src/main/kotlin/org/modelix/model/server/handlers/KeyValueLikeModelServer.kt @@ -11,6 +11,7 @@ import io.ktor.server.resources.get import io.ktor.server.resources.post import io.ktor.server.resources.put import io.ktor.server.response.respondText +import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.routing import io.ktor.util.pipeline.PipelineContext import kotlinx.html.br @@ -211,7 +212,7 @@ class KeyValueLikeModelServer( } @RequiresTransaction - fun collect(rootKey: String, callContext: CallContext?): JSONArray { + fun collect(rootKey: String, routingContext: RoutingContext?): JSONArray { val result = JSONArray() val processed: MutableSet = HashSet() val pending: MutableSet = HashSet() @@ -219,8 +220,8 @@ class KeyValueLikeModelServer( while (pending.isNotEmpty()) { val keys: List = ArrayList(pending) pending.clear() - if (callContext != null) { - keys.forEach { callContext.checkKeyPermission(it, EPermissionType.READ) } + if (routingContext != null) { + keys.forEach { routingContext.checkKeyPermission(it, EPermissionType.READ) } } val values = stores.getGlobalStoreClient(false).getAll(keys) for (i in keys.indices) { @@ -253,7 +254,7 @@ class KeyValueLikeModelServer( } @RequiresTransaction - private fun CallContext.putEntries(newEntries: Map) { + private fun RoutingContext.putEntries(newEntries: Map) { val referencedKeys: MutableSet = HashSet() for ((key, value) in newEntries) { checkKeyPermission(key, EPermissionType.WRITE) @@ -326,7 +327,7 @@ class KeyValueLikeModelServer( } } - private suspend fun CallContext.respondValue(key: String, value: String?) { + private suspend fun RoutingContext.respondValue(key: String, value: String?) { if (value == null) { throw HttpException(HttpStatusCode.NotFound, details = "key '$key' not found") } else { @@ -335,7 +336,7 @@ class KeyValueLikeModelServer( } @Throws(IOException::class) - private fun CallContext.checkKeyPermission(key: String, type: EPermissionType) { + private fun RoutingContext.checkKeyPermission(key: String, type: EPermissionType) { val isWrite = type == EPermissionType.WRITE switchKeyType( key = key, diff --git a/model-server/src/main/kotlin/org/modelix/model/server/handlers/MetricsApiImpl.kt b/model-server/src/main/kotlin/org/modelix/model/server/handlers/MetricsApiImpl.kt index f7f2b24928..411bce118b 100644 --- a/model-server/src/main/kotlin/org/modelix/model/server/handlers/MetricsApiImpl.kt +++ b/model-server/src/main/kotlin/org/modelix/model/server/handlers/MetricsApiImpl.kt @@ -1,13 +1,11 @@ package org.modelix.model.server.handlers import io.ktor.server.application.Application -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.call import io.ktor.server.application.install import io.ktor.server.metrics.micrometer.MicrometerMetrics import io.ktor.server.response.respond +import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.routing -import io.ktor.util.pipeline.PipelineContext import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics import io.micrometer.core.instrument.binder.jvm.JvmInfoMetrics @@ -54,7 +52,7 @@ class MetricsApiImpl : MetricsApi() { } } - override suspend fun PipelineContext.getMetrics() { + override suspend fun RoutingContext.getMetrics() { call.respond(appMicrometerRegistry.scrape()) } } diff --git a/model-server/src/main/kotlin/org/modelix/model/server/handlers/ModelReplicationServer.kt b/model-server/src/main/kotlin/org/modelix/model/server/handlers/ModelReplicationServer.kt index 331d245c35..c3006202a8 100644 --- a/model-server/src/main/kotlin/org/modelix/model/server/handlers/ModelReplicationServer.kt +++ b/model-server/src/main/kotlin/org/modelix/model/server/handlers/ModelReplicationServer.kt @@ -13,9 +13,9 @@ import io.ktor.server.response.respond import io.ktor.server.response.respondBytesWriter import io.ktor.server.response.respondText import io.ktor.server.response.respondTextWriter +import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.routing import io.ktor.util.cio.use -import io.ktor.util.pipeline.PipelineContext import io.ktor.utils.io.ByteWriteChannel import io.ktor.utils.io.close import io.ktor.utils.io.readUTF8Line @@ -90,7 +90,7 @@ class ModelReplicationServer( private fun repositoryId(paramValue: String?) = RepositoryId(checkNotNull(paramValue) { "Parameter 'repository' not available" }) - override suspend fun PipelineContext.getRepositories() { + override suspend fun RoutingContext.getRepositories() { call.respondText( @OptIn(RequiresTransaction::class) runRead { repositoriesManager.getRepositories() } @@ -99,7 +99,7 @@ class ModelReplicationServer( ) } - override suspend fun PipelineContext.getRepositoryBranches(repository: String) { + override suspend fun RoutingContext.getRepositoryBranches(repository: String) { call.respondText( @OptIn(RequiresTransaction::class) runRead { repositoriesManager.getBranchNames(repositoryId(repository)) } @@ -108,7 +108,7 @@ class ModelReplicationServer( ) } - override suspend fun PipelineContext.getRepositoryBranchDelta( + override suspend fun RoutingContext.getRepositoryBranchDelta( repository: String, branch: String, lastKnown: String?, @@ -123,7 +123,7 @@ class ModelReplicationServer( call.respondDelta(RepositoryId(repository), versionHash, lastKnown) } - override suspend fun PipelineContext.getRepositoryBranchV1( + override suspend fun RoutingContext.getRepositoryBranchV1( repository: String, branch: String, lastKnown: String?, @@ -138,7 +138,7 @@ class ModelReplicationServer( call.respond(BranchV1(branch, versionHash)) } - override suspend fun PipelineContext.deleteRepositoryBranch( + override suspend fun RoutingContext.deleteRepositoryBranch( repository: String, branch: String, ) { @@ -162,7 +162,7 @@ class ModelReplicationServer( call.respond(HttpStatusCode.NoContent) } - override suspend fun PipelineContext.getRepositoryBranchHash( + override suspend fun RoutingContext.getRepositoryBranchHash( repository: String, branch: String, ) { @@ -176,7 +176,7 @@ class ModelReplicationServer( call.respondText(versionHash) } - override suspend fun PipelineContext.initializeRepository( + override suspend fun RoutingContext.initializeRepository( repository: String, useRoleIds: Boolean?, legacyGlobalStorage: Boolean?, @@ -194,7 +194,7 @@ class ModelReplicationServer( call.respondDelta(RepositoryId(repository), initialVersion.getContentHash(), null) } - override suspend fun PipelineContext.deleteRepository(repository: String) { + override suspend fun RoutingContext.deleteRepository(repository: String) { checkPermission(ModelServerPermissionSchema.repository(repository).delete) @OptIn(RequiresTransaction::class) @@ -208,7 +208,7 @@ class ModelReplicationServer( } } - override suspend fun PipelineContext.postRepositoryBranch( + override suspend fun RoutingContext.postRepositoryBranch( repository: String, branch: String, ) { @@ -228,7 +228,7 @@ class ModelReplicationServer( call.respondDelta(RepositoryId(repository), mergedHash, deltaFromClient.versionHash) } - override suspend fun PipelineContext.pollRepositoryBranch( + override suspend fun RoutingContext.pollRepositoryBranch( repository: String, branch: String, lastKnown: String?, @@ -239,7 +239,7 @@ class ModelReplicationServer( call.respondDelta(RepositoryId(repository), newVersionHash, lastKnown) } - override suspend fun PipelineContext.postRepositoryObjectsGetAll(repository: String) { + override suspend fun RoutingContext.postRepositoryObjectsGetAll(repository: String) { checkPermission(ModelServerPermissionSchema.repository(repository).objects.read) val channel = call.receiveChannel() val keys = hashSetOf() @@ -263,7 +263,7 @@ class ModelReplicationServer( } } - override suspend fun PipelineContext.pollRepositoryBranchHash( + override suspend fun RoutingContext.pollRepositoryBranchHash( repository: String, branch: String, lastKnown: String?, @@ -275,7 +275,7 @@ class ModelReplicationServer( call.respondText(newVersionHash) } - override suspend fun PipelineContext.getRepositoryVersionHash( + override suspend fun RoutingContext.getRepositoryVersionHash( versionHash: String, repository: String, lastKnown: String?, @@ -287,7 +287,7 @@ class ModelReplicationServer( call.respondDelta(RepositoryId(repository), versionHash, lastKnown) } - override suspend fun PipelineContext.postRepositoryBranchQuery( + override suspend fun RoutingContext.postRepositoryBranchQuery( repository: String, branchName: String, ) { @@ -334,7 +334,7 @@ class ModelReplicationServer( } } - override suspend fun PipelineContext.postRepositoryVersionHashQuery( + override suspend fun RoutingContext.postRepositoryVersionHashQuery( versionHash: String, repository: String, ) { @@ -345,7 +345,7 @@ class ModelReplicationServer( ModelQLServer.handleCall(call, branch.getRootNode(), branch.getArea()) } - override suspend fun PipelineContext.putRepositoryObjects(repository: String) { + override suspend fun RoutingContext.putRepositoryObjects(repository: String) { checkPermission(ModelServerPermissionSchema.repository(parameter("repository")).objects.add) val channel = call.receiveChannel() @@ -374,7 +374,7 @@ class ModelReplicationServer( } @Deprecated("deprecated flag is set in the OpenAPI specification") - override suspend fun PipelineContext.getVersionHash( + override suspend fun RoutingContext.getVersionHash( versionHash: String, lastKnown: String?, ) { @@ -493,7 +493,7 @@ private fun Map.checkValuesNotNull(lazyMessage: (K) -> Any): Map -private fun PipelineContext.parameter(name: String): String { +private fun RoutingContext.parameter(name: String): String { return call.parameter(name) } diff --git a/model-server/src/main/resources/openapi/templates/api.mustache b/model-server/src/main/resources/openapi/templates/api.mustache index 4d47a3c227..74055390f4 100644 --- a/model-server/src/main/resources/openapi/templates/api.mustache +++ b/model-server/src/main/resources/openapi/templates/api.mustache @@ -16,7 +16,7 @@ import io.ktor.server.resources.head import io.ktor.server.resources.patch {{/featureResources}} import io.ktor.server.routing.* -import io.ktor.util.pipeline.PipelineContext +import io.ktor.server.routing.RoutingContext {{#imports}}import {{import}} {{/imports}} @@ -42,7 +42,7 @@ abstract class {{classname}} { {{#isDeprecated}} @Deprecated("deprecated flag is set in the OpenAPI specification") {{/isDeprecated}} - abstract suspend fun PipelineContext.{{operationId}}{{#lambda.titlecase}}{{key}}{{/lambda.titlecase}}({{#allParams}}{{paramName}}: {{{dataType}}}{{^required}}?{{/required}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) + abstract suspend fun RoutingContext.{{operationId}}{{#lambda.titlecase}}{{key}}{{/lambda.titlecase}}({{#allParams}}{{paramName}}: {{{dataType}}}{{^required}}?{{/required}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) {{/entrySet}} {{/x-modelix-media-type-handlers}} @@ -62,7 +62,7 @@ abstract class {{classname}} { {{#isDeprecated}} @Deprecated("deprecated flag is set in the OpenAPI specification") {{/isDeprecated}} - abstract suspend fun PipelineContext.{{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{^required}}?{{/required}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) + abstract suspend fun RoutingContext.{{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{^required}}?{{/required}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) {{/x-modelix-media-type-handlers}} {{/vendorExtensions}} diff --git a/model-server/src/test/kotlin/org/modelix/model/server/ModelServerTestUtil.kt b/model-server/src/test/kotlin/org/modelix/model/server/ModelServerTestUtil.kt index ac09c332a5..3bc312f10f 100644 --- a/model-server/src/test/kotlin/org/modelix/model/server/ModelServerTestUtil.kt +++ b/model-server/src/test/kotlin/org/modelix/model/server/ModelServerTestUtil.kt @@ -4,6 +4,9 @@ import io.ktor.serialization.kotlinx.json.json import io.ktor.server.application.Application import io.ktor.server.application.install import io.ktor.server.application.pluginOrNull +import io.ktor.server.engine.EmbeddedServer +import io.ktor.server.engine.connector +import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty import io.ktor.server.netty.NettyApplicationEngine import io.ktor.server.plugins.contentnegotiation.ContentNegotiation @@ -51,9 +54,12 @@ fun Application.installDefaultServerPlugins(unitTestMode: Boolean = true) { */ fun runWithNettyServer( setupBlock: (application: Application) -> Unit, - testBlock: suspend (server: NettyApplicationEngine) -> Unit, + testBlock: suspend (server: EmbeddedServer) -> Unit, ) { - val nettyServer: NettyApplicationEngine = io.ktor.server.engine.embeddedServer(Netty, port = 0) { + val nettyServer = embeddedServer(Netty, configure = { + connector { this.port = 0 } + responseWriteTimeoutSeconds = 30 + }) { installDefaultServerPlugins() setupBlock(this) } diff --git a/model-server/src/test/kotlin/org/modelix/model/server/handlers/ModelReplicationServerTest.kt b/model-server/src/test/kotlin/org/modelix/model/server/handlers/ModelReplicationServerTest.kt index b1b0d63bc9..c204329411 100644 --- a/model-server/src/test/kotlin/org/modelix/model/server/handlers/ModelReplicationServerTest.kt +++ b/model-server/src/test/kotlin/org/modelix/model/server/handlers/ModelReplicationServerTest.kt @@ -28,6 +28,7 @@ import io.ktor.serialization.kotlinx.KotlinxSerializationConverter import io.ktor.serialization.kotlinx.json.DefaultJson import io.ktor.serialization.kotlinx.json.json import io.ktor.server.application.Application +import io.ktor.server.engine.EmbeddedServer import io.ktor.server.netty.NettyApplicationEngine import io.ktor.server.testing.ApplicationTestBuilder import io.ktor.server.testing.testApplication @@ -287,8 +288,8 @@ class ModelReplicationServerTest { repositoriesManager.createRepository(repositoryId, null) } - suspend fun createClient(server: NettyApplicationEngine): HttpClient { - val port = server.resolvedConnectors().first().port + suspend fun createClient(server: EmbeddedServer): HttpClient { + val port = server.engine.resolvedConnectors().first().port return HttpClient(CIO) { defaultRequest { url("http://localhost:$port") @@ -301,7 +302,7 @@ class ModelReplicationServerTest { val modelReplicationServer = ModelReplicationServer(faultyRepositoriesManager) val setupBlock = { application: Application -> modelReplicationServer.init(application) } - val testBlock: suspend (server: NettyApplicationEngine) -> Unit = { server -> + val testBlock: suspend (server: EmbeddedServer) -> Unit = { server -> withTimeout(10.seconds) { val client = createClient(server) // Act