How to serve directory listings and files with Ktor?

14 Views Asked by At

I need to implement a directory listing similar to the one the Apache server has.

There is a root directory, which is served by default. If a directory is requested, the server responds with the links to the files within the corresponding directory. If a file is requested, the server responds with the file's contents and the appropriate MIME type.

The Ktor's static content serving functionality does not cover my use case.

1

There are 1 best solutions below

0
Aleksei Tirman On

The directory listing can be implemented by defining a tail card route where the requested path can be used for determining the path to the corresponding directory or the file.

The requested path can be retrieved using the name of the tail card parameter by accessing the call.parameters property. The ApplicationCall.respondFile method can be used to respond with the file contents. The directory contents can be listed using the Array<File>.listFiles method and then rendered to an HTML string.

Here is an example implementation:

fun Route.directoryListing(dir: File) {
    get("{listing...}") {
        val requestedPath = call.parameters.getAll("listing")?.joinToString(File.separator) ?: ""
        val targetFile = dir.absoluteFile.resolve(requestedPath).normalize()

        return@get when {
            !targetFile.exists() -> call.respond(HttpStatusCode.NotFound)
            targetFile.isFile -> call.respondFile(targetFile)
            targetFile.isDirectory -> call.respondText(contentType = ContentType.Text.Html) { renderDirectory(targetFile) }
            else -> call.respond(HttpStatusCode.NotImplemented)
        }
    }
}

private fun renderDirectory(dir: File): String {
    val files = dir.listFiles()?.toList() ?: emptyList<File>()
    val sortedFiles = files.sortedBy { it.name }

    return """
<body>
    <h1>${dir.absolutePath}</h1>
    <hr/>
    <pre>
<a href="../">../</a>
${sortedFiles.joinToString(separator = "\n") { """<a href="${it.name}${if (it.isDirectory) "/" else ""}">${it.name}</a>""" }}
    </pre>
</body>
    """.trimIndent()
}

An example usage:

embeddedServer(Netty, port = 5005) {
    routing {
        route("/listing/") { // The trailing slash is important for the anchors in the HTML
            directoryListing(File("."))
        }
    }
}.start(wait = true)