Skip to content

Instantly share code, notes, and snippets.

@corlaez
Last active December 24, 2023 00:30
Show Gist options
  • Save corlaez/51358aa08146bddb3d483f89806a489d to your computer and use it in GitHub Desktop.
Save corlaez/51358aa08146bddb3d483f89806a489d to your computer and use it in GitHub Desktop.
Tiny Kotlin Static Server

This is a tiny kotlin server, useful for simple dev scenarios. Features and limitations:

  • Validation of arguments, no hiding of errors
  • No caching, will read file each time is served (ideal for development server)
  • No dependency or dep management (gradle, maven). Just a single kotlin file and bat scripts
  • Prints all routes that it configures
  • Explicit, it requires you to declare a list of the filesnames to serve
  • No nesting. It will only serve files in the same directory

startServerAfterCompile.bat takes longer to initialize because it first compiles serve.kt to serveKt.class. However, this is only necessary if you are making changes to serve.kt. Use startServer.bat to run the server without compiling (starts faster)

Example startServer.bat output:

Defined route http://localhost:3030/
Defined route http://localhost:3030/index.html
Defined route http://localhost:3030/lote.svg
Defined route http://localhost:3030/lotes.json
import com.sun.net.httpserver.*; import java.io.*; import java.net.*
fun main(args: Array<String>) {
val port = runCatching { args.first().toInt() }.fold({it}) { error("The first arg must be a port number") }
val parent = runCatching { args[1] }.fold({it}) { error("The second arg must be the parent folder number") }
val filenames = runCatching { args.toList().subList(2, args.size) }.fold({it}) { error("At least three args required") }
filenames.forEach { require(File(parent, it).exists()) { "Arg $it is not a valid filename" } }
val server = HttpServer.create(InetSocketAddress(port), 0)
filenames.forEach { rawFilename ->
val filename = rawFilename.removeSuffix(".br")
val encoding = if (rawFilename != filename) " br " else " "
val handler = HttpHandler { exchange ->
if(encoding.isNotBlank()) { exchange.responseHeaders["Content-Encoding"] = encoding.trim() }
runCatching {
exchange.responseHeaders["Content-Type"] = if (filename.endsWith(".json")) "application/json"
else if (filename.endsWith(".svg")) "image/svg+xml"
else if (filename.endsWith(".html")) "text/html"
else "text/plain"
val bytes = File(parent, rawFilename).readBytes()
exchange.sendResponseHeaders(200, bytes.size.toLong())
exchange.responseBody.write(bytes)
}.onFailure {
it.printStackTrace()
exchange.sendResponseHeaders(500, 0)
}
exchange.responseBody.close()
}
fun addRoute(path: String) = println("Defined${encoding}route http://localhost:$port$path")
.also { server.createContext(path).handler = handler }
if (filename == "index.html") addRoute("/")
addRoute("/$filename")
}
server.start()
}
:: Build the classpath (directories used to look for class files)
set "currDir=."
set "kotlinRunner=C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2023.2.2\plugins\Kotlin\kotlinc\lib\kotlin-runner.jar"
set "classpath=%currDir%;%kotlinRunner%"
:: Compress files
C:\brotli.exe --output=brotli/index.html.br --force --quality=11 -- index.html
C:\brotli.exe --output=brotli/lote.svg.br --force --quality=11 -- lote.svg
C:\brotli.exe --output=brotli/lotes.json.br --force --quality=11 -- lotes.json
C:\brotli.exe --output=brotli/zona1.png.br --force --quality=11 -- zona1.png
:: Run our class file with our classpath, pass args
java -cp "%classpath%" ServeKt 3030 brotli index.html.br lote.svg.br lotes.json.br zona1.png.br
:: Build the classpath (directories used to look for class files)
set "currDir=."
set "kotlinRunner=C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2023.2.2\plugins\Kotlin\kotlinc\lib\kotlin-runner.jar"
set "classpath=%currDir%;%kotlinRunner%"
:: Run our class file with our classpath, pass args
java -cp "%classpath%" ServeKt 3030 serve.kt compileKotlin.bat ServeKt.class runServer.bat README.md
:: Compile Kotlin to classfile
kotlinc serve.kt
:: Remove generated folder
rmdir /Q /S META-INF
:: Call script to run server
CALL startServer.bat
@corlaez
Copy link
Author

corlaez commented Dec 24, 2023

The following bat deprecates the rest of the code here. Still, was a good exercise to figure out how to run Kotlin without intellij.

@ECHO OFF
for /f "tokens=*" %%a in ('dir /B') do echo Defined route http://127.0.0.1:3030/%%a
C:\Users\chamaco\.jdks\corretto-21.0.1\bin\jwebserver -p 3030
:: Doesn't have brotli support but could be added with: https://blogs.oracle.com/javamagazine/post/java-18-simple-web-server?source=:em:nw:mt::::RC_WWMK200429P00043C0056:NSL400230298
:: I did have issues trying to serve json when using it from a java program, maybe a permission issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment