How to make Ktor's Application.module() work in GraalVM

172 views Asked by At

I am trying to get started with Ktor and GraalVM. I am trying to run a very dummy Ktor app (IntelliJ's default one) with GraalVM.

I've followed https://ktor.io/docs/graalvm.html#prepare-for-graalvm steps and it is working fine because it configures routing directly from the embeddedServer call.

fun main() {
    embeddedServer(CIO, port = 8080, host = "0.0.0.0") {
        configureRouting()
    }.start(wait = true)

}

However, when I refactored this code to:

fun main() {
    embeddedServer(CIO, port = 8080, host = "0.0.0.0", module = Application::module)
        .start(wait = true)
}

fun Application.module() {
    configureRouting()
}

it stopped working. The error is a reflection issue:

Exception in thread "main" kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: public fun io.ktor.server.application.Application.module(): kotlin.Unit defined in xxxxx[DeserializedSimpleFunctionDescriptor@2ab47469] (member = null)

I've tried a lot of different things in my reflect-config.json file, that actually looks like:

[
  {
    "name": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl",
    "allDeclaredConstructors": true
  },
  {
    "name": "mypackage.Application",
    "methods": [
      {
        "name": "module",
        "parameterTypes": [],
        "returnType": "kotlin.Unit"
      }
    ]
  },
  {
    "name": "io.ktor.server.application.Application",
    "methods": [
      {
        "name": "module",
        "parameterTypes": [],
        "returnType": "kotlin.Unit"
      }
    ]
  },
  {
    "name": "mypackage.plugins.Routing",
    "allDeclaredConstructors": true
  },
  {
    "name": "kotlin.KotlinVersion",
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allDeclaredMethods": true,
    "allDeclaredConstructors": true
  },
  {
    "name": "kotlin.KotlinVersion[]"
  },
  {
    "name": "kotlin.KotlinVersion$Companion"
  },
  {
    "name": "kotlin.KotlinVersion$Companion[]"
  },
  {
    "name": "kotlin.internal.jdk8.JDK8PlatformImplementations",
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allDeclaredMethods": true,
    "allDeclaredConstructors": true
  }
]

my file structure for the project looks like

src/main
├── kotlin
│   └── mypackage
│       ├── Application.kt
│       └── plugins
│           └── Routing.kt
└── resources
    ├── META-INF
    │   └── native-image
    │       └── reflect-config.json
    └── logback.xml

where Application.kt looks like the previous code and Routing.kt is just:

fun Application.configureRouting() {
    routing {
        get("/") {
            call.respondText("Hello World!")
        }
    }
}

I know that the code works when configuring the routing directly on the embeddedServer but delegating in the Application.module() function is way more convenient and mantainable.

If anyone can provide any feedback, it would be great. Thanks in advance!

1

There are 1 answers

0
ozkanpakdil On

In order to build ktor with graalvm you need to run agent like below

java -agentlib:native-image-agent=config-output-dir=./graalcnf/ -jar target/ktor-demo-1.0.1-SNAPSHOT-jar-with-dependencies.jar

This will generate reflect json and other files under graalcnf folder, then you need to provide those in native-image.properties as argument like below

Args = --enable-http \
-H:IncludeResources=.*\\.properties \
  -H:ConfigurationFileDirectories=${project.basedir}/graalcnf/ 

I did this couple of years ago for my test framework which is comparing multiple microservice frameworks like spring quarkus etc. You can reach the example code here and you can see some comparison at here