refactor: add configurable max run concurrency limit for script run routes
This commit is contained in:
@@ -13,11 +13,14 @@ import io.ktor.server.routing.get
|
|||||||
import io.ktor.server.routing.post
|
import io.ktor.server.routing.post
|
||||||
import io.ktor.server.routing.put
|
import io.ktor.server.routing.put
|
||||||
import io.ktor.server.routing.routing
|
import io.ktor.server.routing.routing
|
||||||
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
private const val DEFAULT_PORT = 8080
|
private const val DEFAULT_PORT = 8080
|
||||||
private const val DEFAULT_SCRIPTS_DIR = "scripts"
|
private const val DEFAULT_SCRIPTS_DIR = "scripts"
|
||||||
private const val DEFAULT_HOST = "0.0.0.0"
|
private const val DEFAULT_HOST = "0.0.0.0"
|
||||||
|
private val DEFAULT_MAX_RUN_CONCURRENCY = Runtime.getRuntime().availableProcessors().coerceAtLeast(1)
|
||||||
|
|
||||||
private suspend fun handleSubTokenCreate(call: io.ktor.server.application.ApplicationCall, security: HostSecurity) {
|
private suspend fun handleSubTokenCreate(call: io.ktor.server.application.ApplicationCall, security: HostSecurity) {
|
||||||
val name = call.parameters["name"]
|
val name = call.parameters["name"]
|
||||||
@@ -87,7 +90,7 @@ private suspend fun handleSubTokenDelete(call: io.ktor.server.application.Applic
|
|||||||
call.respondText("deleted subtoken: $name")
|
call.respondText("deleted subtoken: $name")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Application.module(scriptsDir: File, security: HostSecurity) {
|
private fun Application.module(scriptsDir: File, security: HostSecurity, runConcurrencyLimiter: Semaphore) {
|
||||||
routing {
|
routing {
|
||||||
get("/health") {
|
get("/health") {
|
||||||
call.respondText("OK")
|
call.respondText("OK")
|
||||||
@@ -153,16 +156,20 @@ private fun Application.module(scriptsDir: File, security: HostSecurity) {
|
|||||||
val name = call.parameters["script"]
|
val name = call.parameters["script"]
|
||||||
?: return@get call.respondText("missing route name", status = HttpStatusCode.BadRequest)
|
?: return@get call.respondText("missing route name", status = HttpStatusCode.BadRequest)
|
||||||
if (!requireScriptAccess(call, auth, name)) return@get
|
if (!requireScriptAccess(call, auth, name)) return@get
|
||||||
|
runConcurrencyLimiter.withPermit {
|
||||||
handleRunRequest(call, scriptsDir, consumeBody = false)
|
handleRunRequest(call, scriptsDir, consumeBody = false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
post("/run/{script}") {
|
post("/run/{script}") {
|
||||||
val auth = requireAuth(call, security) ?: return@post
|
val auth = requireAuth(call, security) ?: return@post
|
||||||
val name = call.parameters["script"]
|
val name = call.parameters["script"]
|
||||||
?: return@post call.respondText("missing route name", status = HttpStatusCode.BadRequest)
|
?: return@post call.respondText("missing route name", status = HttpStatusCode.BadRequest)
|
||||||
if (!requireScriptAccess(call, auth, name)) return@post
|
if (!requireScriptAccess(call, auth, name)) return@post
|
||||||
|
runConcurrencyLimiter.withPermit {
|
||||||
handleRunRequest(call, scriptsDir, consumeBody = true)
|
handleRunRequest(call, scriptsDir, consumeBody = true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get("/subtokens") {
|
get("/subtokens") {
|
||||||
val auth = requireAuth(call, security) ?: return@get
|
val auth = requireAuth(call, security) ?: return@get
|
||||||
@@ -200,7 +207,7 @@ private fun usage() {
|
|||||||
println(
|
println(
|
||||||
"""
|
"""
|
||||||
Usage:
|
Usage:
|
||||||
./gradlew runWeb --args='[--host=0.0.0.0] [--port=8080] [--scripts-dir=./scripts]'
|
./gradlew runWeb --args='[--host=0.0.0.0] [--port=8080] [--scripts-dir=./scripts] [--max-run-concurrency=N]'
|
||||||
Routes:
|
Routes:
|
||||||
GET /health
|
GET /health
|
||||||
GET /type
|
GET /type
|
||||||
@@ -238,13 +245,17 @@ fun main(args: Array<String>) {
|
|||||||
val port = cli.optionValue("--port=")?.toIntOrNull() ?: DEFAULT_PORT
|
val port = cli.optionValue("--port=")?.toIntOrNull() ?: DEFAULT_PORT
|
||||||
val host = cli.optionValue("--host=")?.ifBlank { DEFAULT_HOST } ?: DEFAULT_HOST
|
val host = cli.optionValue("--host=")?.ifBlank { DEFAULT_HOST } ?: DEFAULT_HOST
|
||||||
val scriptsDir = File(cli.optionValue("--scripts-dir=") ?: DEFAULT_SCRIPTS_DIR).absoluteFile
|
val scriptsDir = File(cli.optionValue("--scripts-dir=") ?: DEFAULT_SCRIPTS_DIR).absoluteFile
|
||||||
|
val maxRunConcurrency = cli.optionValue("--max-run-concurrency=")?.toIntOrNull() ?: DEFAULT_MAX_RUN_CONCURRENCY
|
||||||
|
require(maxRunConcurrency > 0) { "--max-run-concurrency must be > 0" }
|
||||||
|
|
||||||
if (!scriptsDir.exists()) scriptsDir.mkdirs()
|
if (!scriptsDir.exists()) scriptsDir.mkdirs()
|
||||||
val auth = loadOrCreateApiToken(scriptsDir)
|
val auth = loadOrCreateApiToken(scriptsDir)
|
||||||
val security = createHostSecurity(scriptsDir, auth.token)
|
val security = createHostSecurity(scriptsDir, auth.token)
|
||||||
|
val runConcurrencyLimiter = Semaphore(maxRunConcurrency)
|
||||||
|
|
||||||
println("Starting script web host on http://$host:$port")
|
println("Starting script web host on http://$host:$port")
|
||||||
println("Scripts directory: ${scriptsDir.absolutePath}")
|
println("Scripts directory: ${scriptsDir.absolutePath}")
|
||||||
|
println("Run concurrency limit: $maxRunConcurrency")
|
||||||
println("Auth token source: ${auth.source}")
|
println("Auth token source: ${auth.source}")
|
||||||
when {
|
when {
|
||||||
auth.source.startsWith("env:") -> println("Auth token loaded from environment variable.")
|
auth.source.startsWith("env:") -> println("Auth token loaded from environment variable.")
|
||||||
@@ -255,6 +266,6 @@ fun main(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
embeddedServer(Netty, port = port, host = host) {
|
embeddedServer(Netty, port = port, host = host) {
|
||||||
module(scriptsDir, security)
|
module(scriptsDir, security, runConcurrencyLimiter)
|
||||||
}.start(wait = true)
|
}.start(wait = true)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user