I'm hitting the same issue as asked in <https://st...
# ktor
m
I'm hitting the same issue as asked in https://stackoverflow.com/q/64369594/775894 - with a routing defined like this:
Copy code
routing {
  static("/") {
    staticRootFolder = dataDir
    files(".")
    default("index.html")
  }
}
Ktor serves the top-level
index.html
, but when requesting a subdirectory, Ktor returns a 404 (but requesting explicitly
index.html
in this subdirectory works fine). I also tried
default(File(dataDir, "index.html"))
, assuming it would at least serve the top-level
index.html
, which isn't the case. Is there any way to serve those index files in subdirectories?
I also tried adding recursively a bunch of sub-routes like this:
Copy code
private fun Routing.addStaticSubdirectory(directory: File, parent: File) {
  val routePath = if (directory == parent) {
    "/"
  } else {
    directory.absolutePath.removePrefix(parent.absolutePath)
  }
  static(routePath) {
    directory.listFiles()?.filter { it.isDirectory }?.forEach {
      this@addStaticSubdirectory.addStaticSubdirectory(it, directory)
    }

    files(directory)
    default(File(directory, "index.html"))
  }
}
But I get the same behaviour - the default only applies to the top-level directory, not subdirectories. I'm done for the night but my next try will be to remove
files()
and try adding every file to see how that goes...
a
So do you want to serve some local file in each subdirectory if no file is requested? If so here is the solution:
Copy code
fun main() {
    embeddedServer(Netty, port = 3333) {
        routing {
            static("/export") {
                files("export", "index.html")
                default("index.html")
            }
        }
    }.start(wait = true)
}

fun Route.files(folder: String, fallbackFilePath: String) {
    val dir = File(folder)
    val combinedDir = staticRootFolder?.resolve(dir) ?: dir
    get("{static_path...}") {
        val relativePath = call.parameters.getAll("static_path")?.joinToString(File.separator) ?: return@get
        val file = combinedDir.combineSafe(relativePath)
        val localFile = if (file.isDirectory) File(fallbackFilePath) else file
        call.respond(LocalFileContent(localFile, ContentType.defaultForFile(localFile)))
    }
}
m
The route somehow doesn't seem to get triggered for directories (adding a log as a first line in the
get
block gets displayed for files but not directories)
(I have to admit, I'm not familiar with the
{static_path...}
syntax at all)
But yes overall the logic seems to be what I'm after - if the request is for a folder, serve the
index.html
file in that folder, otherwise serve whatever was requested. I'm just not sure why those routes aren't apparently matching folders when requested...
a
Could you please try this one?
Copy code
fun Route.files(folder: String, fallbackFileName: String) {
    val dir = File(folder)
    val combinedDir = staticRootFolder?.resolve(dir) ?: dir
    get("{static_path...}") {
        val relativePath = call.parameters.getAll("static_path")?.joinToString(File.separator) ?: return@get
        val file = combinedDir.combineSafe(relativePath)
        val fallbackFile = file.combineSafe(fallbackFileName)

        val localFile = when {
            file.isFile -> file
            file.isDirectory && fallbackFile.isFile -> fallbackFile
            else -> return@get
        }

        call.respond(LocalFileContent(localFile, ContentType.defaultForFile(localFile)))
    }
}
I though you want to serve the same file when a subdirectory is requested but it turns out you want to serve a file with specified name from each subdirectory.
m
I get a very similar behaviour - any request for a folder (e.g.
/foo/
, but
/
is caught) isn't caught by the
get
block - which results in a 404 regardless of the content of that block
a
You can install the IgnoreTrailingSlash plugin to treat paths with trailing slash as if it’s not present.
m
Oh, good one, I didn't know Ktor was behaving like this by default 🙂
So with that and the solution above, it mostly works - I just don't get the root
index.html
, but it does work for subfolders
And my whole
routing
block is this:
Copy code
static {
          files(dataDir.absolutePath, "index.html")
          default("index.html")
        }
(Requesting
/index.html
explicitly works fine)
Okay, fixed with
default(File(dataDir, "index.html"))
. Thanks a lot!
I'm curious, what is the
get("{static_path...}")
syntax called/where can I find documentation about that?
Ha I take that back - scrolling a bit down on the page you linked above has the explanation already!