読者です 読者をやめる 読者になる 読者になる

謎言語使いの徒然

適当に気になった技術や言語を流すブログ。

Xitrum web framework 弄ってみた

日記 OSS Scala

何か ScalaJPXitrum という単語があったので使ってみた。

まず新しいプロジェクトを作ろうか。

curl -L -o xitrum-new.zip https://github.com/xitrum-framework/xitrum-new/archive/master.zip

後は解凍して終わり。

インストールが必要ないのは楽でいいね。

中身を見れば大体分かる。

README.rst
build.sbt
screenshot.png

./config:
akka.conf
application.conf
flash_socket_policy.xml
logback.xml
ssl_example.crt
ssl_example.key
xitrum.conf

./project:
build.properties
plugins.sbt

./public:
404.html
500.html
app.css
favicon.ico
robots.txt
whale.png

./sbt:
sbt
sbt-launch-0.13.5.jar
sbt.bat

./script:
runner
runner.bat
scalive
scalive-1.2.jar
scalive.bat

./src:
main

./src/main:
scala
scalate

./src/main/scala:
quickstart

./src/main/scala/quickstart:
Boot.scala
action

./src/main/scala/quickstart/action:
DefaultLayout.scala
Errors.scala
SiteIndex.scala

./src/main/scalate:
quickstart

./src/main/scalate/quickstart:
action

./src/main/scalate/quickstart/action:
DefaultLayout.jade
NotFoundError.jade
ServerError.jade
SiteIndex.jade

そのままだよね。

デフォルトでは model に該当するクラスは用意されていない。好きに作ればいいのかな?

構成が殆んどプレーンな sbt なので、ScalaActiveRecord 突っ込めば色々楽ができるだろうね。

Controller

この辺を見る。

  • ./src/main/scala/quickstart/action

まぁファイル名を見れば分かるでしょう。

DefaultLayout.scala : Controller 規定

package quickstart.action

import xitrum.Action

trait DefaultLayout extends Action {
  override def layout = renderViewNoLayout[DefaultLayout]()
}

Action クラスの継承がコントローラクラスの条件のようだ。layout は View 名を省略したときに自動で適用される View テンプレートの選択ロジックに見える。

trait なのが気になるがまぁいい。

Errors.scala

package quickstart.action

import xitrum.annotation.{Error404, Error500}

@Error404
class NotFoundError extends DefaultLayout {
  def execute() {
    respondView()
  }
}

@Error500
class ServerError extends DefaultLayout {
  def execute() {
    respondView()
  }
}

DefaultLayout を継承してコントローラを作成している。execute メソッドがコントローラ本体。この構成はぱっと見 PHPSmarty にも見える。

コントローラクラスの定義が class である事を考えると、アクセスごとにインスタンスを生成している可能性がある?

てなことで、SiteIndex.scala の中身が

package quickstart.action

import xitrum.annotation.GET
import java.rmi.server.UID

@GET("")
class SiteIndex extends DefaultLayout {

  lazy val uid = new UID()

  def execute() {
    println(s"Instance UNIQUE ID: $uid")
    respondView()
  }
}

で、起動(sbt run)してログを,,,,,ってぇ!

8GB メモリのカスタム MacBookAir でまさかの OutOfMemory ... byobu + Chromeでタブ3枚でダメか…。

仕方ないので 16GB の窓8.1で再チャレンジ。

凄まじく大量の resolve... 落ちた原因はこれか。

で、起動して早速 http://localhost:8000 に...

[WARN] Caught exception
java.lang.StringIndexOutOfBoundsException: String index out of range: 1
        at java.lang.String.charAt(String.java:658) ~[na:1.7.0_45]
        at java.util.regex.Matcher.appendReplacement(Matcher.java:762) ~[na:1.7.0_45]
        at java.util.regex.Matcher.replaceAll(Matcher.java:906) ~[na:1.7.0_45]
        at java.lang.String.replaceAll(String.java:2162) ~[na:1.7.0_45]
        at xitrum.util.ClassFileLoader.loadClassData(ClassFileLoader.scala:39) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.util.ClassFileLoader.findClass(ClassFileLoader.scala:26) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.util.ClassFileLoader.loadClass(ClassFileLoader.scala:17) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Dispatcher$.devDispatch(Dispatcher.scala:139) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Dispatcher$.dispatch(Dispatcher.scala:68) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Dispatcher.channelRead0(Dispatcher.scala:176) ~[xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Dispatcher.channelRead0(Dispatcher.scala:154) ~[xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.MethodOverrider.channelRead0(MethodOverrider.scala:45) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.MethodOverrider.channelRead0(MethodOverrider.scala:19) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.UriParser.channelRead0(UriParser.scala:33) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.UriParser.channelRead0(UriParser.scala:16) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.WebJarsServer.channelRead0(WebJarsServer.scala:33) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.WebJarsServer.channelRead0(WebJarsServer.scala:23) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.PublicFileServer.channelRead0(PublicFileServer.scala:33) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.PublicFileServer.channelRead0(PublicFileServer.scala:23) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.BaseUrlRemover.channelRead0(BaseUrlRemover.scala:23) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.BaseUrlRemover.channelRead0(BaseUrlRemover.scala:11) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at xitrum.handler.inbound.Request2Env.sendUpstream(Request2Env.scala:283) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Request2Env.channelRead0(Request2Env.scala:96) [xitrum_2.11-3.14.jar:3.14]
        at xitrum.handler.inbound.Request2Env.channelRead0(Request2Env.scala:44) [xitrum_2.11-3.14.jar:3.14]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:103) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:163) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:787) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:507) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:464) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:378) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:350) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116) [netty-all-4.0.20.Final.jar:4.0.20.Final]
        at java.lang.Thread.run(Thread.java:744) [na:1.7.0_45]

|||orz 俺が書いた部分が問題なのかと思って、修正した部分を削ったが状況は変わらず…。

ぱっと見 routing の名前解決で落ちてるのか?

てことで

package quickstart.action

import xitrum.annotation.GET
import java.rmi.server.UID

@GET("/")
class SiteIndex extends DefaultLayout {

  def execute() {
    respondView()
  }
}

でもダメだった

org.fusesource.scalate.TemplateException: temp file rename failed

で起動失敗…2014/06/29 20:54 現在の master で発生…。

今のバージョン特有の問題なのか…?

バージョン番号は xitrum-new の 1.0-SNAPSHOT ...

SNAPSHOT だしなぁ

routing は慣れれば楽だが、class 数が爆発しそうな気がしてはいる。事実上の routing は annotation で指定している。

レスポンスの仕様は ここ 見ればわかるけど、結構綺麗だ。

テンプレートエンジンは scalate を使用。

同様に、このテンプレートの仕様を眺めていると、1アクション1テンプレート構成を徹底しているらしい。つまり、1アクションの中で複数のテンプレートを使いたければ、Routing アノテーションを付与しない複数の Action クラスを定義して、respondView[HomeAction_NormalUser]()とか叩くらしい。

一つのアクションで複数のテンプレートを返すという挙動は設計的にどうなのとは思うけど、これはこれで冗長だなぁ。

Playframework の implicit マジックの雨霰とどっちがいいかは好みが分かれそうな。