Ktor標準ではThymeleafに対応していなかったので、Thymeleaf使えるかどうか試してみた感じです。
※Ktor1.2以降からThymeleaf機能が追加されたようです→Ktor 1.2.0(rc)で追加されたThymeleaf Featureを試してみた - しおしお
Thymeleaf用のFeatureを作る
ktor/FreeMarker.kt at master · ktorio/ktor · GitHubを参考に…
やってることは、こんな感じです。
ClassLoaderTemplateResolver
の生成と初期設定
- アプリケーションからの戻りが
ThymeleafContent
の場合に、Thymeleafを使ったレンダリング
import io.ktor.application.ApplicationCallPipeline
import io.ktor.application.ApplicationFeature
import io.ktor.http.content.OutgoingContent
import io.ktor.response.ApplicationSendPipeline
import io.ktor.util.AttributeKey
import io.ktor.util.cio.bufferedWriter
import kotlinx.coroutines.io.ByteWriteChannel
import org.thymeleaf.TemplateEngine
import org.thymeleaf.context.Context
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
class ThymeleafContent(
val template: String,
val model: Map<String, Any>?
)
class Thymeleaf(private val engine: TemplateEngine) {
companion object Feature : ApplicationFeature<ApplicationCallPipeline, ClassLoaderTemplateResolver, Thymeleaf> {
override val key: AttributeKey<Thymeleaf> = AttributeKey("thymeleaf")
override fun install(pipeline: ApplicationCallPipeline,
configure: ClassLoaderTemplateResolver.() -> Unit): Thymeleaf {
val config = ClassLoaderTemplateResolver(Thread.currentThread().contextClassLoader)
.apply {
prefix = "templates/"
suffix = ".html"
characterEncoding = "UTF-8"
}
.apply(configure)
val engine = TemplateEngine()
engine.setTemplateResolver(config)
val feature = Thymeleaf(engine)
pipeline.sendPipeline.intercept(ApplicationSendPipeline.Transform) { value ->
if (value is ThymeleafContent) {
val response = feature.process(value)
proceedWith(response)
}
}
return feature
}
}
private fun process(content: ThymeleafContent): ThymeleafOutgoingContent {
return ThymeleafOutgoingContent(
engine,
content
)
}
private class ThymeleafOutgoingContent(
val engine: TemplateEngine,
val content: ThymeleafContent
) : OutgoingContent.WriteChannelContent() {
override suspend fun writeTo(channel: ByteWriteChannel) {
channel.bufferedWriter(Charsets.UTF_8).use {
val context = Context().apply {
setVariables(content.model)
}
engine.process(content.template, context, it)
}
}
}
}
アプリケーションを作る
サーバサイド
- 上で作ったThymeleaf Featureを使えるようにするためにインストールする
- 処理結果として、ThymeleafContentを返す
package siosio
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.response.respond
import io.ktor.routing.get
import io.ktor.routing.routing
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
@Suppress("unused")
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(Thymeleaf)
routing {
get("/hello") {
call.respond(ThymeleafContent("index", mapOf("user" to User(1, "しおしお"))))
}
}
}
data class User(val id: Long, val name: String)
Thymeleaf用テンプレート
単純にUserクラスの内容を出力しているだけですね
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org" xml:lang="ja" lang="ja">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li th:text="${user.id}"></li>
<li th:text="${user.name}"></li>
</ul>
</body>
</html>
実行結果
動きましたね(๑•̀ㅂ•́)و✧