You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Ktor实现类似Apache的目录列表与文件服务?

实现Ktor自定义Apache风格目录列表服务

核心思路

绕过Ktor自带的静态内容服务,手动拦截请求:

  • 将请求路径映射到本地文件系统的根目录
  • 若请求目标是文件,返回文件内容并自动匹配MIME类型
  • 若请求目标是目录,生成带链接的HTML列表页面,模拟Apache的目录展示效果

实现步骤

1. 基础依赖配置

确保build.gradle.kts中包含Ktor核心依赖:

dependencies {
    implementation("io.ktor:ktor-server-core-jvm")
    implementation("io.ktor:ktor-server-netty-jvm")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
    testImplementation("io.ktor:ktor-server-tests-jvm")
}

2. 核心路由逻辑

在Ktor的Application模块中编写处理逻辑,重点处理路径映射、文件/目录判断、响应生成:

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.io.File
import java.nio.file.Files

fun Application.module() {
    // 定义服务的根目录,可根据实际场景修改
    val rootDir = File(System.getProperty("user.dir") + "/web-root").apply {
        if (!exists()) mkdirs()
    }

    routing {
        get("{...}") {
            // 解析请求路径,去除前缀斜杠
            val requestPath = call.request.path().removePrefix("/")
            val targetFile = File(rootDir, requestPath).canonicalFile

            // 安全校验:防止路径穿越攻击,确保目标文件在根目录范围内
            if (!targetFile.startsWith(rootDir.canonicalFile)) {
                call.respond(HttpStatusCode.Forbidden, "Access denied")
                return@get
            }

            if (!targetFile.exists()) {
                call.respond(HttpStatusCode.NotFound, "File not found")
                return@get
            }

            if (targetFile.isFile) {
                // 返回文件内容,并自动匹配MIME类型
                val mimeType = Files.probeContentType(targetFile.toPath()) ?: ContentType.Application.OctetStream
                call.respondFile(targetFile, contentType = ContentType.parse(mimeType))
            } else if (targetFile.isDirectory) {
                // 生成目录列表HTML页面
                val dirContent = targetFile.listFiles()?.sortedWith(compareBy({ !it.isDirectory }, { it.name })) ?: emptyArray()
                val htmlContent = buildString {
                    appendln("<!DOCTYPE html>")
                    appendln("<html>")
                    appendln("<head><title>Index of /$requestPath</title></head>")
                    appendln("<body>")
                    appendln("<h1>Index of /$requestPath</h1>")
                    appendln("<hr>")
                    appendln("<ul>")
                    // 非根目录时添加上级目录链接
                    if (requestPath.isNotEmpty()) {
                        val parentPath = requestPath.substringBeforeLast("/", "")
                        appendln("<li><a href=\"/$parentPath\">../</a></li>")
                    }
                    // 遍历目录内的文件和子目录,生成链接
                    dirContent.forEach { file ->
                        val fileUrl = "/${requestPath.takeIf { it.isNotEmpty() }?.let { "$it/" }.orEmpty()}${file.name}"
                        val displayName = if (file.isDirectory) "${file.name}/" else file.name
                        appendln("<li><a href=\"$fileUrl\">$displayName</a></li>")
                    }
                    appendln("</ul>")
                    appendln("<hr>")
                    appendln("</body></html>")
                }
                call.respondText(htmlContent, contentType = ContentType.Text.Html)
            }
        }
    }
}

3. 关键细节说明

  • 路径安全:通过canonicalFile和startsWith校验,彻底避免恶意路径穿越
  • MIME类型匹配:使用Files.probeContentType自动识别文件类型,无法识别时 fallback到二进制流
  • 目录排序:先显示子目录(带/后缀),再按文件名排序,和Apache默认行为一致
  • 导航优化:非根目录时显示../上级目录链接,提升操作便捷性

启动测试

  1. 在项目根目录创建web-root文件夹,放入测试文件或子目录
  2. 启动Ktor服务器,访问http://localhost:8080即可看到目录列表,点击文件可直接查看内容

内容的提问来源于stack exchange,提问作者Aleksei Tirman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 23:15:17