Reputation: 23
I found example of Ktor GraalVM https://ktor.io/docs/graalvm.html which works perfectly well. But how to serve static resources like images or even more important js (javascript) files?
I have:
routing {
get("/") {
call.respondText("Hello GraalVM!")
call.application.environment.log.info("Call made to /")
}
static("/") {
resources("img")
}
}
The new code is 'static("/")...' part and the question again: Is it possible to serve static content? Of course I still want to have native GraalVM output (executable).
http://0.0.0.0:8080/ works and serves: "Hello GraalVM!" output. But http://0.0.0.0:8080/img/test.png (in resources -> img folder I have a file test.png) returns 404 not found.
Upvotes: 0
Views: 442
Reputation: 23
I've made it work... and found why it is not working with only classical code:
static("/") {
staticBasePackage = "static"
static("img") {
resources("img")
}
}
...but first, fragments of the call, i.e. logs:
>>> url.protocol = resource
and here's the answer ... it normally works as a file, but in native GraalVM this is sought as a resource. Original code:
@InternalAPI
public fun resourceClasspathResource(url: URL, path: String, mimeResolve: (String) -> ContentType): OutgoingContent? {
return when (url.protocol) {
"file" -> {
val file = File(url.path.decodeURLPart())
if (file.isFile) LocalFileContent(file, mimeResolve(file.extension)) else null
}
"jar" -> {
if (path.endsWith("/")) {
null
} else {
val zipFile = findContainingJarFile(url.toString())
val content = JarFileContent(zipFile, path, mimeResolve(url.path.extension()))
if (content.isFile) content else null
}
}
"jrt" -> {
URIFileContent(url, mimeResolve(url.path.extension()))
}
else -> null
}
}
and here, as you can see, the resource is not supported here. I had to rewrite the code like this:
// "new version" of: io.ktor.server.http.content.StaticContentResolutionKt.resourceClasspathResource
fun resourceClasspathResourceVersion2(url: URL, path: String, mimeResolve: (String) -> ContentType, classLoader: ClassLoader): OutgoingContent? {
println(">>> url.protocol = ${url.protocol}")
return when (url.protocol) {
"file" -> {
val file = File(url.path.decodeURLPart())
println(">>> file = $file")
if (file.isFile) {
val localFileContent = LocalFileContent(file, mimeResolve(file.extension))
println(">>> localFileContent = $localFileContent")
localFileContent
} else null
}
// ... here are other things which are in original version
"resource" -> {
println(">>> in resource")
val resourceName = url.path.substring(1)
println(">>> resourceName = $resourceName")
val resourceAsStream = classLoader.getResourceAsStream(resourceName)
val cnt = ByteArrayContent(resourceAsStream.readAllBytes(), ContentType.parse("image/gif"), HttpStatusCode.OK)
cnt
}
else -> null
}
}
All the code for the Routing class (Routing.kt) looks like this:
package io.ktorgraal.plugins
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.io.File
import java.net.URL
fun Application.configureRouting() {
// I use println not some Logger to make it simple... in native also
// (there are different problems in native compilations with loggers)
// "new version" of: io.ktor.server.http.content.StaticContentResolutionKt.resourceClasspathResource
fun resourceClasspathResourceVersion2(url: URL, path: String, mimeResolve: (String) -> ContentType, classLoader: ClassLoader): OutgoingContent? {
println(">>> url.protocol = ${url.protocol}")
return when (url.protocol) {
"file" -> {
val file = File(url.path.decodeURLPart())
println(">>> file = $file")
if (file.isFile) {
val localFileContent = LocalFileContent(file, mimeResolve(file.extension))
println(">>> localFileContent = $localFileContent")
localFileContent
} else null
}
// ... here are other things which are in original version
"resource" -> {
println(">>> in resource")
val resourceName = url.path.substring(1)
println(">>> resourceName = $resourceName")
val resourceAsStream = classLoader.getResourceAsStream(resourceName)
val cnt = ByteArrayContent(resourceAsStream.readAllBytes(), ContentType.parse("image/gif"), HttpStatusCode.OK)
cnt
}
else -> null
}
}
// Starting point for a Ktor app:
routing {
get("/") {
call.respondText("Hello GraalVM!")
call.application.environment.log.info("Call made to /")
}
// not working... so we need below "reimplementation"
/*static("/") {
staticBasePackage = "static"
static("img") {
resources("img")
}
}*/
// "new version" of: io.ktor.server.http.content.StaticContentKt.resources
// ... because I need to call different function no resourceClasspathResource, but resourceClasspathResourceVersion2
get("/{pathParameterName...}") {
println(">>> this endpoint was called")
val relativePath = call.parameters.getAll("pathParameterName")?.joinToString(File.separator) ?: return@get
val mimeResolve: (String) -> ContentType = { ContentType.defaultForFileExtension(it) }
val classLoader: ClassLoader = application.environment.classLoader
val normalizedPath = relativePath // here I do not do normalization although in Ktor code such code exists
println(">>> normalizedPath = $normalizedPath")
val resources = classLoader.getResources(normalizedPath)
for (url in resources.asSequence()) {
println(">>> url = $url")
resourceClasspathResourceVersion2(url, normalizedPath, mimeResolve, classLoader)?.let { content ->
if (content != null) {
println(">>> about to return content")
call.respond(content)
}
}
}
}
}
}
And here, too, I had to have a "new version" of io.ktor.server.http.content.StaticContentKt.resources not to change the entire library. Then the
http://localhost:8080/static/img/test/test.gif
call works and returns a file.
The whole code change "https://github.com/ktorio/ktor-samples/tree/main/graalvm" is the above listed Routing.kt class. You should also add a file such as gif to resources -> static -> img -> test -> test.gif (nesting only to show the structure).
Upvotes: 0