even better ktor server integration

This commit is contained in:
Sergey Chernov 2022-12-09 11:51:00 +01:00
parent 5cd4f00b1d
commit 652950633f
4 changed files with 85 additions and 14 deletions

View File

@ -14,6 +14,12 @@ It also contains useful tools for this type of application:
- `AccessControlObject` to contain recoverable password-protected data with a backup `secret` word to restore access.
Will be published under MIT when become usable.
## Usage
Currently, complie it and publisj into mavenLocal, and use from there. Soon will be published to some public maven repo.
## License
Will be published under MIT as soon as first RC will be published.

View File

@ -60,7 +60,11 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
}
}
val jvmMain by getting
val jvmMain by getting {
dependencies {
implementation("io.ktor:ktor-server-core:$ktor_version")
}
}
val jvmTest by getting {
dependencies {
implementation("io.ktor:ktor-server-core:$ktor_version")

View File

@ -1,8 +1,10 @@
package net.sergeych.superlogin.server
import io.ktor.server.application.*
import net.sergeych.parsec3.AdapterBuilder
import net.sergeych.parsec3.CommandHost
import net.sergeych.parsec3.WithAdapter
import net.sergeych.parsec3.parsec3TransportServer
import net.sergeych.superlogin.*
import net.sergeych.unikrypto.SignedRecord
import kotlin.random.Random
@ -12,9 +14,61 @@ fun randomACOLike(): ByteArray {
}
/**
* Create a superlogin server on a curretn adapter builder, using some session builder lambda.
* Create superlogin server using default parsec 3 server transport providing session creation
* lambda and a block to set up server-specific api. The session must inherit [SLServerSession]
* with application-specific data, and implement all of its abstract methods at list (also it
* might need t override some default methods, see docs. For example:
* ~~~
*
* // Application-specific API declaration
* class TestApiServer<T : WithAdapter> : CommandHost<T>() {
* val foobar by command<Unit, String?>()
* }
*
* // application data to be included in the "user" data transfered to and from the client:
* data class TestData(clientData: String)
*
* // session state, is kept until the user connection (e.g. session) is closed, in the RAM:
* class TestSession(var sessionData: String) : SLServerSession<TestData>()
*
* // Create a server in a ktor context:
* fun Application.serverModule() {
* SuperloginServer(TestApiServer<TestSession>(), { TestSession("foobar") }) {
* // application API implementation
* on(api.foobar) {
* "Session data: ${sessionData}, loginName: $currentLoginName user data: $superloginData"
* }
* }
* }
* ~~~
*
* @param api service specific API declaration (usually in the shared code)
* @param sessionBuilder function that creates new session. Session is created on every incoming
* connection so try not to do any time and resource consuming operations there
* @param adapterBuilder block that should implement service-specific api.
* @param D application specific data (included in the session and transferred with registration
* and login)
* @param T service-specific session, class implementing any session-based code specific to the
* application but also implement necessary `superlogin` methods (implementing abstract
* ones) that implement user login data persistence and search.
*/
inline fun <reified D, T : SLServerSession<D>,> Application.SuperloginServer(
api: CommandHost<T>,
crossinline sessionBuilder: suspend AdapterBuilder<T, CommandHost<T>>.()->T,
crossinline adapterBuilder: AdapterBuilder<T, CommandHost<T>>.()->Unit
) {
parsec3TransportServer(api) {
setupSuperloginServer { sessionBuilder() }
adapterBuilder()
}
}
/**
* Set up a superlogin server manually, on a current adapter builder, using some session builder lambda.
* Note that session must inherit [SLServerSession] and implement necessary abstract methods
* that do store and retrieve user data. It sould be used like:
* that do store and retrieve user data. Usually you can do it easier with [SuperloginServer] call.
* If you want to do it manually (fr example using a custom transport), do it like this:
* ```
* parsec3TransportServer(TestApiServer<TestSession>()) {
* // the session extends SLServerSession and contain app-specific data and
@ -31,8 +85,8 @@ fun randomACOLike(): ByteArray {
*
* @param sessionBuilder code that builds a new session over the adapter.
*/
inline fun <reified D, T : SLServerSession<D>, H : CommandHost<T>> AdapterBuilder<T, H>.superloginServer(
crossinline sessionBuilder: suspend AdapterBuilder<T,H>.()->T
inline fun <reified D, T : SLServerSession<D>, H : CommandHost<T>> AdapterBuilder<T, H>.setupSuperloginServer(
crossinline sessionBuilder: suspend AdapterBuilder<T, H>.() -> T,
) {
newSession { sessionBuilder() }
addErrors(SuperloginExceptionsRegistry)

View File

@ -5,16 +5,13 @@ import io.ktor.server.engine.*
import io.ktor.server.netty.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import net.sergeych.parsec3.CommandHost
import net.sergeych.parsec3.Parsec3WSClient
import net.sergeych.parsec3.WithAdapter
import net.sergeych.parsec3.parsec3TransportServer
import net.sergeych.parsec3.*
import net.sergeych.superlogin.*
import net.sergeych.superlogin.client.LoginState
import net.sergeych.superlogin.client.Registration
import net.sergeych.superlogin.client.SuperloginClient
import net.sergeych.superlogin.server.SLServerSession
import net.sergeych.superlogin.server.superloginServer
import net.sergeych.superlogin.server.setupSuperloginServer
import net.sergeych.unikrypto.PublicKey
import superlogin.assertThrowsAsync
import kotlin.random.Random
@ -193,6 +190,7 @@ internal class WsServerKtTest {
assertTrue { slc.isLoggedIn }
assertEquals("foo", slc.call(api.loginName))
}
}
@ -212,10 +210,19 @@ internal class WsServerKtTest {
}
inline fun <reified D, T : SLServerSession<D>, H : CommandHost<T>> Application.SuperloginServer(
api: H,
crossinline sessionBuilder: suspend AdapterBuilder<T, H>.()->T,
crossinline adapterBuilder: AdapterBuilder<T, H>.()->Unit
) {
parsec3TransportServer(api) {
setupSuperloginServer { sessionBuilder() }
adapterBuilder()
}
}
fun Application.testServerModule() {
parsec3TransportServer(TestApiServer<TestSession>()) {
// superloginServer(TestServerTraits,TestApiServer<SLServerSession<TestData>>()) {
superloginServer { TestSession() }
SuperloginServer(TestApiServer<TestSession>(), { TestSession() }) {
on(api.loginName) {
println("login name called. now we have $currentLoginName : $superloginData")
currentLoginName