Refactor server API
This commit is contained in:
parent
20b20a621f
commit
c8141c6338
@ -2,6 +2,7 @@ package space.kscience.visionforge.examples
|
|||||||
|
|
||||||
import io.ktor.server.cio.CIO
|
import io.ktor.server.cio.CIO
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
|
import io.ktor.server.http.content.resources
|
||||||
import io.ktor.server.routing.routing
|
import io.ktor.server.routing.routing
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import space.kscience.dataforge.context.Global
|
import space.kscience.dataforge.context.Global
|
||||||
@ -10,6 +11,7 @@ import space.kscience.visionforge.VisionManager
|
|||||||
import space.kscience.visionforge.html.VisionPage
|
import space.kscience.visionforge.html.VisionPage
|
||||||
import space.kscience.visionforge.html.formFragment
|
import space.kscience.visionforge.html.formFragment
|
||||||
import space.kscience.visionforge.onPropertyChange
|
import space.kscience.visionforge.onPropertyChange
|
||||||
|
import space.kscience.visionforge.server.EngineConnectorConfig
|
||||||
import space.kscience.visionforge.server.close
|
import space.kscience.visionforge.server.close
|
||||||
import space.kscience.visionforge.server.openInBrowser
|
import space.kscience.visionforge.server.openInBrowser
|
||||||
import space.kscience.visionforge.server.visionPage
|
import space.kscience.visionforge.server.visionPage
|
||||||
@ -17,10 +19,16 @@ import space.kscience.visionforge.server.visionPage
|
|||||||
fun main() {
|
fun main() {
|
||||||
val visionManager = Global.fetch(VisionManager)
|
val visionManager = Global.fetch(VisionManager)
|
||||||
|
|
||||||
val server = embeddedServer(CIO, 7777, "localhost") {
|
|
||||||
|
val connector = EngineConnectorConfig("localhost", 7777)
|
||||||
|
|
||||||
|
val server = embeddedServer(CIO, connector.port, connector.host) {
|
||||||
|
|
||||||
routing {
|
routing {
|
||||||
visionPage(visionManager, VisionPage.scriptHeader("js/visionforge-playground.js")) {
|
resources()
|
||||||
|
}
|
||||||
|
|
||||||
|
visionPage(connector, visionManager, VisionPage.scriptHeader("js/visionforge-playground.js")) {
|
||||||
val form = formFragment("form") {
|
val form = formFragment("form") {
|
||||||
label {
|
label {
|
||||||
htmlFor = "fname"
|
htmlFor = "fname"
|
||||||
@ -58,7 +66,7 @@ fun main() {
|
|||||||
println(this)
|
println(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}.start(false)
|
}.start(false)
|
||||||
|
|
||||||
server.openInBrowser()
|
server.openInBrowser()
|
||||||
|
@ -15,6 +15,7 @@ import space.kscience.dataforge.meta.Null
|
|||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.visionforge.Colors
|
import space.kscience.visionforge.Colors
|
||||||
import space.kscience.visionforge.html.VisionPage
|
import space.kscience.visionforge.html.VisionPage
|
||||||
|
import space.kscience.visionforge.server.EngineConnectorConfig
|
||||||
import space.kscience.visionforge.server.close
|
import space.kscience.visionforge.server.close
|
||||||
import space.kscience.visionforge.server.openInBrowser
|
import space.kscience.visionforge.server.openInBrowser
|
||||||
import space.kscience.visionforge.server.visionPage
|
import space.kscience.visionforge.server.visionPage
|
||||||
@ -36,21 +37,26 @@ fun main() {
|
|||||||
color.set(Colors.white)
|
color.set(Colors.white)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val connector = EngineConnectorConfig("localhost", 7777)
|
||||||
|
|
||||||
val server = embeddedServer(CIO,7777, "localhost") {
|
val server = embeddedServer(CIO, connector.port, connector.host) {
|
||||||
routing {
|
routing {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
resources()
|
resources()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
visionPage(solids.visionManager, VisionPage.threeJsHeader, VisionPage.styleSheetHeader("css/styles.css")) {
|
visionPage(
|
||||||
|
connector,
|
||||||
|
solids.visionManager, VisionPage.threeJsHeader,
|
||||||
|
VisionPage.styleSheetHeader("css/styles.css")
|
||||||
|
) {
|
||||||
div("flex-column") {
|
div("flex-column") {
|
||||||
h1 { +"Satellite detector demo" }
|
h1 { +"Satellite detector demo" }
|
||||||
vision { sat }
|
vision { sat }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}.start(false)
|
}.start(false)
|
||||||
|
|
||||||
server.openInBrowser()
|
server.openInBrowser()
|
||||||
|
@ -4,9 +4,8 @@ import io.ktor.http.URLProtocol
|
|||||||
import io.ktor.server.application.install
|
import io.ktor.server.application.install
|
||||||
import io.ktor.server.cio.CIO
|
import io.ktor.server.cio.CIO
|
||||||
import io.ktor.server.engine.ApplicationEngine
|
import io.ktor.server.engine.ApplicationEngine
|
||||||
|
import io.ktor.server.engine.EngineConnectorConfig
|
||||||
import io.ktor.server.engine.embeddedServer
|
import io.ktor.server.engine.embeddedServer
|
||||||
import io.ktor.server.routing.Routing
|
|
||||||
import io.ktor.server.routing.route
|
|
||||||
import io.ktor.server.util.url
|
import io.ktor.server.util.url
|
||||||
import io.ktor.server.websocket.WebSockets
|
import io.ktor.server.websocket.WebSockets
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -26,8 +25,8 @@ import space.kscience.visionforge.html.HtmlFormFragment
|
|||||||
import space.kscience.visionforge.html.HtmlVisionFragment
|
import space.kscience.visionforge.html.HtmlVisionFragment
|
||||||
import space.kscience.visionforge.html.VisionCollector
|
import space.kscience.visionforge.html.VisionCollector
|
||||||
import space.kscience.visionforge.html.visionFragment
|
import space.kscience.visionforge.html.visionFragment
|
||||||
import space.kscience.visionforge.server.VisionRouteConfiguration
|
import space.kscience.visionforge.server.EngineConnectorConfig
|
||||||
import space.kscience.visionforge.server.require
|
import space.kscience.visionforge.server.VisionRoute
|
||||||
import space.kscience.visionforge.server.serveVisionData
|
import space.kscience.visionforge.server.serveVisionData
|
||||||
import space.kscience.visionforge.visionManager
|
import space.kscience.visionforge.visionManager
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@ -48,8 +47,6 @@ public class VFForNotebook(override val context: Context) : ContextAware, Corout
|
|||||||
|
|
||||||
public val visionManager: VisionManager = context.visionManager
|
public val visionManager: VisionManager = context.visionManager
|
||||||
|
|
||||||
private val configuration = VisionRouteConfiguration(visionManager)
|
|
||||||
|
|
||||||
private var counter = 0
|
private var counter = 0
|
||||||
|
|
||||||
private var engine: ApplicationEngine? = null
|
private var engine: ApplicationEngine? = null
|
||||||
@ -68,7 +65,7 @@ public class VFForNotebook(override val context: Context) : ContextAware, Corout
|
|||||||
|
|
||||||
public fun startServer(
|
public fun startServer(
|
||||||
host: String = context.properties["visionforge.host"].string ?: "localhost",
|
host: String = context.properties["visionforge.host"].string ?: "localhost",
|
||||||
port: Int = context.properties["visionforge.port"].int ?: VisionRouteConfiguration.DEFAULT_PORT,
|
port: Int = context.properties["visionforge.port"].int ?: VisionRoute.DEFAULT_PORT,
|
||||||
): MimeTypedResult = html {
|
): MimeTypedResult = html {
|
||||||
if (engine != null) {
|
if (engine != null) {
|
||||||
p {
|
p {
|
||||||
@ -77,6 +74,8 @@ public class VFForNotebook(override val context: Context) : ContextAware, Corout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val connector: EngineConnectorConfig = EngineConnectorConfig(host, port)
|
||||||
|
|
||||||
engine?.stop(1000, 2000)
|
engine?.stop(1000, 2000)
|
||||||
engine = context.embeddedServer(CIO, port, host) {
|
engine = context.embeddedServer(CIO, port, host) {
|
||||||
install(WebSockets)
|
install(WebSockets)
|
||||||
@ -119,6 +118,8 @@ public class VFForNotebook(override val context: Context) : ContextAware, Corout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
engine.application.serveVisionData(VisionRoute(cellRoute, visionManager), collector)
|
||||||
|
|
||||||
visionFragment(
|
visionFragment(
|
||||||
context,
|
context,
|
||||||
embedData = true,
|
embedData = true,
|
||||||
@ -126,13 +127,6 @@ public class VFForNotebook(override val context: Context) : ContextAware, Corout
|
|||||||
collector = collector,
|
collector = collector,
|
||||||
fragment = fragment
|
fragment = fragment
|
||||||
)
|
)
|
||||||
|
|
||||||
engine.application.require(Routing) {
|
|
||||||
route(cellRoute) {
|
|
||||||
serveVisionData(TODO(), collector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//if not, use static rendering
|
//if not, use static rendering
|
||||||
visionFragment(context, fragment = fragment)
|
visionFragment(context, fragment = fragment)
|
||||||
|
@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
import kotlinx.html.stream.createHTML
|
||||||
import space.kscience.dataforge.context.Context
|
import space.kscience.dataforge.context.Context
|
||||||
import space.kscience.dataforge.context.ContextAware
|
import space.kscience.dataforge.context.ContextAware
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
@ -30,12 +31,16 @@ import space.kscience.visionforge.VisionChange
|
|||||||
import space.kscience.visionforge.VisionManager
|
import space.kscience.visionforge.VisionManager
|
||||||
import space.kscience.visionforge.flowChanges
|
import space.kscience.visionforge.flowChanges
|
||||||
import space.kscience.visionforge.html.*
|
import space.kscience.visionforge.html.*
|
||||||
import java.awt.Desktop
|
|
||||||
import java.net.URI
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
|
||||||
public enum class DataServeMode {
|
public class VisionRoute(
|
||||||
|
public val route: String,
|
||||||
|
public val visionManager: VisionManager,
|
||||||
|
override val meta: ObservableMutableMeta = MutableMeta(),
|
||||||
|
) : Configurable, ContextAware {
|
||||||
|
|
||||||
|
public enum class Mode {
|
||||||
/**
|
/**
|
||||||
* Embed the initial state of the vision inside its html tag.
|
* Embed the initial state of the vision inside its html tag.
|
||||||
*/
|
*/
|
||||||
@ -52,11 +57,6 @@ public enum class DataServeMode {
|
|||||||
UPDATE
|
UPDATE
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VisionRouteConfiguration(
|
|
||||||
public val visionManager: VisionManager,
|
|
||||||
override val meta: ObservableMutableMeta = MutableMeta(),
|
|
||||||
) : Configurable, ContextAware {
|
|
||||||
|
|
||||||
override val context: Context get() = visionManager.context
|
override val context: Context get() = visionManager.context
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,7 +64,7 @@ public class VisionRouteConfiguration(
|
|||||||
*/
|
*/
|
||||||
public var updateInterval: Long by meta.long(300, key = UPDATE_INTERVAL_KEY)
|
public var updateInterval: Long by meta.long(300, key = UPDATE_INTERVAL_KEY)
|
||||||
|
|
||||||
public var dataMode: DataServeMode by meta.enum(DataServeMode.UPDATE)
|
public var dataMode: Mode by meta.enum(Mode.UPDATE)
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val DEFAULT_PORT: Int = 7777
|
public const val DEFAULT_PORT: Int = 7777
|
||||||
@ -78,11 +78,17 @@ public class VisionRouteConfiguration(
|
|||||||
* Serve visions in a given [route] without providing a page template.
|
* Serve visions in a given [route] without providing a page template.
|
||||||
* [visions] could be changed during the service.
|
* [visions] could be changed during the service.
|
||||||
*/
|
*/
|
||||||
public fun Route.serveVisionData(
|
public fun Application.serveVisionData(
|
||||||
configuration: VisionRouteConfiguration,
|
configuration: VisionRoute,
|
||||||
resolveVision: (Name) -> Vision?,
|
resolveVision: (Name) -> Vision?,
|
||||||
) {
|
) {
|
||||||
application.log.info("Serving visions at ${this@serveVisionData}")
|
require(WebSockets)
|
||||||
|
routing {
|
||||||
|
route(configuration.route) {
|
||||||
|
install(CORS) {
|
||||||
|
anyHost()
|
||||||
|
}
|
||||||
|
application.log.info("Serving visions at ${configuration.route}")
|
||||||
|
|
||||||
//Update websocket
|
//Update websocket
|
||||||
webSocket("ws") {
|
webSocket("ws") {
|
||||||
@ -132,11 +138,14 @@ public fun Route.serveVisionData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public fun Route.serveVisionData(
|
public fun Application.serveVisionData(
|
||||||
configuration: VisionRouteConfiguration,
|
configuration: VisionRoute,
|
||||||
cache: VisionCollector,
|
cache: VisionCollector,
|
||||||
): Unit = serveVisionData(configuration) { cache[it]?.second }
|
): Unit = serveVisionData(configuration) { cache[it]?.second }
|
||||||
|
|
||||||
//
|
//
|
||||||
///**
|
///**
|
||||||
// * Compile a fragment to string and serve visions from it
|
// * Compile a fragment to string and serve visions from it
|
||||||
@ -161,24 +170,18 @@ public fun Route.serveVisionData(
|
|||||||
/**
|
/**
|
||||||
* Serve a page, potentially containing any number of visions at a given [route] with given [header].
|
* Serve a page, potentially containing any number of visions at a given [route] with given [header].
|
||||||
*/
|
*/
|
||||||
public fun Route.visionPage(
|
public fun Application.visionPage(
|
||||||
configuration: VisionRouteConfiguration,
|
route: String,
|
||||||
|
configuration: VisionRoute,
|
||||||
|
connector: EngineConnectorConfig,
|
||||||
headers: Collection<HtmlFragment>,
|
headers: Collection<HtmlFragment>,
|
||||||
visionFragment: HtmlVisionFragment,
|
visionFragment: HtmlVisionFragment,
|
||||||
) {
|
) {
|
||||||
application.require(WebSockets)
|
require(WebSockets)
|
||||||
require(CORS) {
|
|
||||||
anyHost()
|
|
||||||
}
|
|
||||||
|
|
||||||
val visionCache: VisionCollector = mutableMapOf()
|
val collector: VisionCollector = mutableMapOf()
|
||||||
serveVisionData(configuration, visionCache)
|
|
||||||
|
|
||||||
//filled pages
|
val html = createHTML().apply {
|
||||||
get {
|
|
||||||
//re-create html and vision list on each call
|
|
||||||
call.respondHtml {
|
|
||||||
val callbackUrl = call.url()
|
|
||||||
head {
|
head {
|
||||||
meta {
|
meta {
|
||||||
charset = "utf-8"
|
charset = "utf-8"
|
||||||
@ -191,61 +194,61 @@ public fun Route.visionPage(
|
|||||||
//Load the fragment and remember all loaded visions
|
//Load the fragment and remember all loaded visions
|
||||||
visionFragment(
|
visionFragment(
|
||||||
context = configuration.context,
|
context = configuration.context,
|
||||||
embedData = configuration.dataMode == DataServeMode.EMBED,
|
embedData = configuration.dataMode == VisionRoute.Mode.EMBED,
|
||||||
fetchDataUrl = if (configuration.dataMode != DataServeMode.EMBED) {
|
fetchDataUrl = if (configuration.dataMode != VisionRoute.Mode.EMBED) {
|
||||||
URLBuilder(callbackUrl).apply {
|
url {
|
||||||
pathSegments = pathSegments + "data"
|
host = connector.host
|
||||||
}.buildString()
|
port = connector.port
|
||||||
|
path(route, "data")
|
||||||
|
}
|
||||||
} else null,
|
} else null,
|
||||||
updatesUrl = if (configuration.dataMode == DataServeMode.UPDATE) {
|
updatesUrl = if (configuration.dataMode == VisionRoute.Mode.UPDATE) {
|
||||||
URLBuilder(callbackUrl).apply {
|
url {
|
||||||
protocol = URLProtocol.WS
|
protocol = URLProtocol.WS
|
||||||
pathSegments = pathSegments + "ws"
|
host = connector.host
|
||||||
}.buildString()
|
port = connector.port
|
||||||
|
path(route, "ws")
|
||||||
|
}
|
||||||
} else null,
|
} else null,
|
||||||
visionCache = visionCache,
|
visionCache = collector,
|
||||||
fragment = visionFragment
|
fragment = visionFragment
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}.finalize()
|
||||||
|
|
||||||
|
//serve data
|
||||||
|
serveVisionData(configuration, collector)
|
||||||
|
|
||||||
|
//filled pages
|
||||||
|
routing {
|
||||||
|
get(route) {
|
||||||
|
call.respondText(html, ContentType.Text.Html)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun Route.visionPage(
|
public fun Application.visionPage(
|
||||||
|
connector: EngineConnectorConfig,
|
||||||
visionManager: VisionManager,
|
visionManager: VisionManager,
|
||||||
vararg headers: HtmlFragment,
|
vararg headers: HtmlFragment,
|
||||||
configurationBuilder: VisionRouteConfiguration.() -> Unit = {},
|
route: String = "/",
|
||||||
|
configurationBuilder: VisionRoute.() -> Unit = {},
|
||||||
visionFragment: HtmlVisionFragment,
|
visionFragment: HtmlVisionFragment,
|
||||||
) {
|
) {
|
||||||
val configuration = VisionRouteConfiguration(visionManager).apply(configurationBuilder)
|
val configuration = VisionRoute(route, visionManager).apply(configurationBuilder)
|
||||||
visionPage(configuration, listOf(*headers), visionFragment)
|
visionPage(route, configuration, connector, listOf(*headers), visionFragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render given [VisionPage] at server
|
* Render given [VisionPage] at server
|
||||||
*/
|
*/
|
||||||
public fun Route.visionPage(page: VisionPage, configurationBuilder: VisionRouteConfiguration.() -> Unit = {}) {
|
public fun Application.visionPage(
|
||||||
val configuration = VisionRouteConfiguration(page.visionManager).apply(configurationBuilder)
|
connector: EngineConnectorConfig,
|
||||||
visionPage(configuration, page.pageHeaders.values, visionFragment = page.content)
|
page: VisionPage,
|
||||||
|
route: String = "/",
|
||||||
|
configurationBuilder: VisionRoute.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
val configuration = VisionRoute(route, page.visionManager).apply(configurationBuilder)
|
||||||
|
visionPage(route, configuration, connector, page.pageHeaders.values, visionFragment = page.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun <P : Pipeline<*, ApplicationCall>, B : Any, F : Any> P.require(
|
|
||||||
plugin: Plugin<P, B, F>,
|
|
||||||
configure: B.() -> Unit = {},
|
|
||||||
): F = pluginOrNull(plugin) ?: install(plugin, configure)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to a given Ktor server using browser
|
|
||||||
*/
|
|
||||||
public fun ApplicationEngine.openInBrowser() {
|
|
||||||
val connector = environment.connectors.first()
|
|
||||||
val uri = URI("http", null, connector.host, connector.port, null, null, null)
|
|
||||||
Desktop.getDesktop().browse(uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the server with default timeouts
|
|
||||||
*/
|
|
||||||
public fun ApplicationEngine.close(): Unit = stop(1000, 5000)
|
|
@ -0,0 +1,38 @@
|
|||||||
|
package space.kscience.visionforge.server
|
||||||
|
|
||||||
|
import io.ktor.server.application.ApplicationCall
|
||||||
|
import io.ktor.server.application.Plugin
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.application.pluginOrNull
|
||||||
|
import io.ktor.server.engine.ApplicationEngine
|
||||||
|
import io.ktor.server.engine.EngineConnectorBuilder
|
||||||
|
import io.ktor.server.engine.EngineConnectorConfig
|
||||||
|
import io.ktor.util.pipeline.Pipeline
|
||||||
|
import java.awt.Desktop
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
|
||||||
|
public fun <P : Pipeline<*, ApplicationCall>, B : Any, F : Any> P.require(
|
||||||
|
plugin: Plugin<P, B, F>,
|
||||||
|
): F = pluginOrNull(plugin) ?: install(plugin)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to a given Ktor server using browser
|
||||||
|
*/
|
||||||
|
public fun ApplicationEngine.openInBrowser() {
|
||||||
|
val connector = environment.connectors.first()
|
||||||
|
val uri = URI("http", null, connector.host, connector.port, null, null, null)
|
||||||
|
Desktop.getDesktop().browse(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the server with default timeouts
|
||||||
|
*/
|
||||||
|
public fun ApplicationEngine.close(): Unit = stop(1000, 5000)
|
||||||
|
|
||||||
|
|
||||||
|
public fun EngineConnectorConfig(host: String, port: Int): EngineConnectorConfig = EngineConnectorBuilder().apply {
|
||||||
|
this.host = host
|
||||||
|
this.port = port
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user