App EngineでUnfiltered filterとScalateを使う場合のメモです。
Unfiltered filter
クラスパスに依存関係を追加するだけでOKです。App Engine特有の考慮は必要ありませんでした。spin-upも早いので問題なさそうです。
import unfiltered.request._ import unfiltered.response._ class App extends unfiltered.filter.Plan { def intent = { case GET(Path("/something")) => ResponseString("Hello") } }
Scalate
いくつか考慮が必要です。
Scalateは実行時にテンプレートをコンパイルする機能があります。App Engineでは(開発サーバも含めて)ファイルの書き込みが禁止されているため、例外が発生します。そのため、あらかじめテンプレートをコンパイルしてクラスパスに配置しておく必要があります。
- TemplateEngineの初期化時に以下を設定する必要があります。これをやらないと実行時にコンパイルしようとして例外が発生します。
- allowReload = falseに設定します。
- resourceLoaderにServletResourceLoaderを設定します。
- workingDirectoryを設定します。どこでも構いません。
- ハマった場合はデバッガでステップ実行すると解決しやすいです。
- あらかじめテンプレートをコンパイルしておく必要があります。
- sbtではxsbt-scalate-generateがあります。
- Gradleではgradle-scalate-pluginがあります。自分でビルドスクリプトを書くこともできます。
- spin-upは比較的早いようです。
Unfilteredと組み合わせる方法はいくつかあるようですが、テンプレートの生成結果が小さいサイズであればStringで受け取る方法が最も簡単です。App Engineでは定期的にJVMが再起動するので、ヒープメモリへの影響は比較的少ないのではないかと。
import java.io.File import org.fusesource.scalate.servlet.ServletResourceLoader import org.fusesource.scalate.TemplateEngine import unfiltered.request._ import unfiltered.response._ class App extends unfiltered.filter.Plan { def intent = { case GET(Path("/something")) => HtmlContent ~> ResponseString(scalate.layout("/something.mustache", Map("key" -> "value"))) } lazy val scalate = { val engine = new TemplateEngine engine.workingDirectory = new File("WEB-INF") engine.resourceLoader = new ServletResourceLoader(config.getServletContext) engine.allowReload = false engine } }
(1/17追記) レスポンスのContent-Typeを設定するように修正しました。
(1/17追記) 下記のようにcase classを定義してもおk。
def intent = { case GET(Path("/something")) => Scalate("/something.mustache", Map("key" -> "value")) } case class Scalate(path: String, params: Map[String, Any]) extends ComposeResponse(HtmlContent ~> ResponseString(scalate.layout(path, params)))
Gradleでテンプレートをコンパイルする場合のビルドスクリプトも載せておきます。
dependencies { def scalaVersion = '2.10.3' compile 'org.fusesource.scalate:scalate-core_2.10:1.6.1' compile "org.scala-lang:scala-library:$scalaVersion" compile "org.scala-lang:scala-compiler:$scalaVersion" } task compileScalate << { def cl = Thread.currentThread().contextClassLoader configurations.runtime.each { f -> cl.addURL(f.toURI().toURL()) } cl.loadClass('org.fusesource.scalate.support.Precompiler').newInstance().with { sources_$eq(file('src/main/webapp')) contextClass_$eq('org.fusesource.scalate.DefaultRenderContext') targetDirectory_$eq(sourceSets.main.output.classesDir) execute() } } tasks.classes.dependsOn(tasks.compileScalate)
Unfilteredはドキュメントはいまいちですが、ルーティングのコードが分かりやすいのでいい感じ。Scalateもテンプレートの選択肢が多いのでいいですね。