Refactor package structure and add web components.

Reorganized Kotlin package structure under 'nl.astraeus' and removed 'gradle.properties' file. Updated '.gitignore' to exclude 'gradle.properties'. Added new web functionalities including ID generation, websocket and HTTP request handling to support dynamic web content delivery. Adjusted server configuration and modified build version.
This commit is contained in:
2024-12-01 11:59:26 +01:00
parent bf9d72a20c
commit 88d09ecdbf
13 changed files with 245 additions and 42 deletions

1
.gitignore vendored
View File

@@ -44,3 +44,4 @@ bin/
### .kotlin ###
.kotlin
kotlin-js-store
gradle.properties

View File

@@ -5,7 +5,7 @@ plugins {
}
group = "nl.astraeus"
version = "1.0.0-SNAPSHOT"
version = "0.1.0-SNAPSHOT"
repositories {
mavenCentral()

View File

@@ -1 +0,0 @@
kotlin.code.style=official

View File

@@ -0,0 +1,19 @@
package nl.astraeus.tmpl
import kotlinx.browser.document
import kotlinx.html.div
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
class HelloKomponent : Komponent() {
override fun HtmlBuilder.render() {
div {
+ "Hello, world!"
}
}
}
fun main() {
Komponent.create(document.body!!, HelloKomponent())
}

View File

@@ -0,0 +1,69 @@
package nl.astraeus.tmpl
import com.zaxxer.hikari.HikariConfig
import io.undertow.Undertow
import io.undertow.UndertowOptions
import io.undertow.predicate.Predicates
import io.undertow.server.handlers.encoding.ContentEncodingRepository
import io.undertow.server.handlers.encoding.EncodingHandler
import io.undertow.server.handlers.encoding.GzipEncodingProvider
import nl.astraeus.logger.Logger
import nl.astraeus.tmpl.db.Database
import nl.astraeus.tmpl.web.RequestHandler
val log = Logger()
val REPO_NAME = "dummy so the gitea template compiles, please remove"
val SERVER_PORT = 7001
val JDBC_PORT = 8001
fun main() {
Thread.currentThread().uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
log.warn(e) {
e.message
}
}
Runtime.getRuntime().addShutdownHook(
object : Thread() {
override fun run() {
Database.vacuumDatabase()
Database.closeDatabase()
}
}
)
Class.forName("nl.astraeus.jdbc.Driver")
Database.initialize(HikariConfig().apply {
driverClassName = "nl.astraeus.jdbc.Driver"
jdbcUrl = "jdbc:stat:webServerPort=$JDBC_PORT:jdbc:sqlite:data/${REPO_NAME}.db"
username = "sa"
password = ""
maximumPoolSize = 25
isAutoCommit = false
validate()
})
val compressionHandler =
EncodingHandler(
ContentEncodingRepository()
.addEncodingHandler(
"gzip",
GzipEncodingProvider(), 50,
Predicates.parse("max-content-size(5)")
)
).setNext(RequestHandler)
val server = Undertow.builder()
.addHttpListener(SERVER_PORT, "localhost")
.setIoThreads(4)
.setHandler(compressionHandler)
.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 1000)
.build()
println("Starting undertow server at port 6007...")
server?.start()
}

View File

@@ -1,4 +1,4 @@
package tmpl.db
package nl.astraeus.tmpl.db
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource

View File

@@ -1,6 +1,6 @@
package tmpl.db
package nl.astraeus.tmpl.db
import tmpl.log
import nl.astraeus.tmpl.log
import java.sql.Connection
import java.sql.SQLException
import java.sql.Timestamp

View File

@@ -0,0 +1,16 @@
package nl.astraeus.tmpl.web
import java.security.SecureRandom
val idChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
val random = SecureRandom()
fun generateId(length: Int = 8): String {
val id = StringBuilder()
repeat(length) {
id.append(idChars[random.nextInt(idChars.length)])
}
return id.toString()
}

View File

@@ -0,0 +1,41 @@
package nl.astraeus.tmpl.web
import kotlinx.html.body
import kotlinx.html.head
import kotlinx.html.html
import kotlinx.html.meta
import kotlinx.html.script
import kotlinx.html.stream.appendHTML
import kotlinx.html.title
import nl.astraeus.tmpl.REPO_NAME
fun generateIndex(patch: String?): String {
val result = StringBuilder();
if (patch == null) {
result.appendHTML(true).html {
head {
title("${REPO_NAME}")
//link("/css/all.min.css", "stylesheet")
}
body {
script(src = "/${REPO_NAME}.js") {}
}
}
} else {
result.appendHTML(true).html {
head {
title("${REPO_NAME}")
meta {
httpEquiv = "refresh"
content = "0; url=/$itemUrl/$patch"
}
}
body {
+"Redirecting to $itemUrl $patch..."
}
}
}
return result.toString()
}

View File

@@ -0,0 +1,39 @@
package nl.astraeus.tmpl.web
import io.undertow.server.HttpHandler
import io.undertow.server.HttpServerExchange
import io.undertow.server.handlers.PathHandler
import io.undertow.server.handlers.resource.PathResourceManager
import io.undertow.server.handlers.resource.ResourceHandler
import java.nio.file.Paths
import kotlin.text.startsWith
val itemUrl = "blaat"
object IndexHandler : HttpHandler {
override fun handleRequest(exchange: HttpServerExchange) {
if (exchange.requestPath.startsWith("/$itemUrl/")) {
exchange.responseSender.send(generateIndex(null))
} else {
val itemId = generateId()
exchange.responseSender.send(generateIndex(itemId))
}
}
}
object RequestHandler : HttpHandler {
val resourceHandler = ResourceHandler(PathResourceManager(Paths.get("web")))
val pathHandler = PathHandler(resourceHandler)
init {
pathHandler.addExactPath("/", IndexHandler)
pathHandler.addExactPath("/index.html", IndexHandler)
pathHandler.addPrefixPath("/song", IndexHandler)
pathHandler.addExactPath("/ws", WebsocketConnectHandler)
}
override fun handleRequest(exchange: HttpServerExchange) {
pathHandler.handleRequest(exchange)
}
}

View File

@@ -0,0 +1,55 @@
package nl.astraeus.tmpl.web
import io.undertow.Handlers.websocket
import io.undertow.server.HttpHandler
import io.undertow.server.HttpServerExchange
import io.undertow.websockets.WebSocketConnectionCallback
import io.undertow.websockets.core.AbstractReceiveListener
import io.undertow.websockets.core.BufferedBinaryMessage
import io.undertow.websockets.core.BufferedTextMessage
import io.undertow.websockets.core.WebSocketChannel
import io.undertow.websockets.extensions.PerMessageDeflateHandshake
import io.undertow.websockets.spi.WebSocketHttpExchange
import kotlin.also
class WebsocketHandler : AbstractReceiveListener(), WebSocketConnectionCallback {
// var user: String? = null
override fun onConnect(exchange: WebSocketHttpExchange, channel: WebSocketChannel) {
channel.receiveSetter.set(this)
channel.resumeReceives()
}
override fun onFullTextMessage(channel: WebSocketChannel, message: BufferedTextMessage) {
val data = message.data
TODO("handle text message")
}
override fun onFullBinaryMessage(channel: WebSocketChannel, message: BufferedBinaryMessage?) {
message?.data?.also { data ->
var length = 0
for (buffer in data.resource) {
length += buffer.remaining()
}
val bytes = ByteArray(length)
var offset = 0
for (buffer in data.resource) {
buffer.get(bytes, offset, buffer.remaining())
offset += buffer.remaining()
}
TODO("handle binary message")
}
}
}
object WebsocketConnectHandler : HttpHandler {
override fun handleRequest(exchange: HttpServerExchange) {
val handshakeHandler = websocket(WebsocketHandler())
handshakeHandler.addExtension(PerMessageDeflateHandshake())
handshakeHandler.handleRequest(exchange)
}
}

View File

@@ -1,37 +0,0 @@
package tmpl
import com.zaxxer.hikari.HikariConfig
import nl.astraeus.logger.Logger
import tmpl.db.Database
val log = Logger()
fun main() {
Thread.currentThread().uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
log.warn(e) {
e.message
}
}
Runtime.getRuntime().addShutdownHook(
object : Thread() {
override fun run() {
Database.vacuumDatabase()
Database.closeDatabase()
}
}
)
Class.forName("nl.astraeus.jdbc.Driver")
Database.initialize(HikariConfig().apply {
driverClassName = "nl.astraeus.jdbc.Driver"
jdbcUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/kotlin-server-web-undertow.db"
username = "sa"
password = ""
maximumPoolSize = 25
isAutoCommit = false
validate()
})
}

1
web/web.txt Normal file
View File

@@ -0,0 +1 @@
Directory where the web content (html + css + resources) will be placed.