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") // Referenced in application.conf @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クラスの内容を出力しているだけですね
<!DOCTYPE html > <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> <!--/*@thymesVar id="user" type="siosio.User"*/--> <ul> <li th:text="${user.id}"></li> <li th:text="${user.name}"></li> </ul> </body> </html>
実行結果
動きましたね(๑•̀ㅂ•́)و✧