ラベル Framework の投稿を表示しています。 すべての投稿を表示
ラベル Framework の投稿を表示しています。 すべての投稿を表示

2013年12月25日水曜日

GAE + SBT + Unfiltered + Scalate


GAEの環境でUnfilteredScalateを使ってみました。

SBTでGAEを開発するときの設定はこちらを参照。
SBTでUnfilteredの設定をするにはこちらを参照。

Unfilteredのintentメソッドの実装

まず、前回の復習から。

GAEでは、サーブレットを使って開発するので、サーブレット対応のUnfilteredであるunfiltered-filterパッケージを使用します。

unfiltered-filterを使った基本的な開発の流れ

  1. unfiltered.filter.Planを継承したフィルターを作る
  2. intentメソッドを実装する
  3. web.xmlにフィルターを登録する

unfiltered.filter.Planは、javax.servlet.Filterを継承しています。javax.servlet.Filterを継承したクラスは、doFilterメソッドを実装しないといけないんですが、それはunfiltered.filter.Planクラスで実装されているので実装する必要はありません。その代わりにintentメソッドを実装しなければいけません。

intentメソッドは、unfiltered.request.HttpRequest[HttpServletRequest]を受け取り、unfiltered.response.ResponseFunction[HttpServletResponse]を返すPartialFunctionを返すメソッドです。PartialFunctionになっているので、リクエストの処理をmatch式でやろうというわけですね。

unfiltered.request.HttpRequestは、HttpServletRequestのラッパーで、unfiltered.response.ResponseFunctionは、HttpServletResponseOutputStreamにレスポンスを書き込むトレイトです。

レスポンスを返すには、unfiltered.response.ResponseFunctionを実装したインスタンスを返さなければいけないんですが、それが面倒な人の為にちゃんと予め実装されたサブクラスが幾つか用意されいます。よく使うであろうと思われるのは、ResponseStringでしょう。これを使ってintentメソッドを実装すると、例えば次のようになります。

class Filter extends unfiltered.filter.Plan {
  def intent = {
    case Path("/index.html") => new ResponseString( "Hoge" )
  }
}

web.xmlへの登録は、前回やったので、スキップしま~す。

GAEでのUnfiltered

今回は、Scalateを使ってレスポンスを返すので、ResponseStringは使えません。なぜなら、Scalateの処理をするためには、HttpServletResponseが持っているOutputStreamインスタンスが必要だからです。少なくとも上記の例では、OutputStreamは登場してません。

そこでScalateを使うために、unfiltered.response.ResponseFunctionのサブトレイトResponseWriterを使います。このトレイトは、writeメソッドを実装する必要があるます。このwriteメソッドは、引数としてHttpServletResponseOutputStreamWriterを取るので、このメソッドの中でOutputStreamを使ってScalateの処理を行うことができます。

intentメソッド内でScalateを使う

Scalateとは

Scalateとは、テンプレートエンジンです。いろいろな書式のテンプレートに対応しています。
  • Jade
  • Mustache
  • Scaml(HamlのScala版)
  • SSP(Scala Server Page, JSPのScala版)
  • Scuery

基本的な使い方

Scalateの基本的な使い方は、とても簡単です。TemplateEngineのインスタンスを生成して、layoutメソッドを呼び出すだけです。layoutは、String型のHTMLを返します。

val engine = new TemplateEngine
val html = engine.layout("index.ssp"Map("name" -> "Hoge")List(Biding("name""String")))

layoutは、テンプレートのソースコードをコンパイルして一時領域にクラスファイルを出力します。キャッシュが効くので、テンプレートのソースコードに変更がなければ、2度目移行の呼び出しは、コンパイルは回避されて、一時領域のクライスファイルを使ってHTMLを出力します。

layoutメソッドの詳細
def layout (uri: String, attributes: Map[String, Any] = Map(), extraBindings: Traversable[Binding] = Nil): String
引数:
  • uri
  • Ssp(Scala Server Page)やScaml(HamlのScala版)のテンプレートのソースコードのパス。
  • attributes
  • URIの場所にあるテンプレートに渡す値。複数渡せます。上記の例では、Stringの"Hoge"という値をnameという変数で渡しています。テンプレート側で使用するには、テンプレート側でnameという変数を宣言する必要があります。次の引数のextraBindingsを渡せば宣言も必要なくなります。
  • extraBindings
  • テンプレート側で暗黙的に使える変数の宣言。上記の例では、nameという変数をテンプレート側で宣言せずに使えるようになります。

ただし、GAEのような環境で使用するには、上記では上手く行きません。サーブレット環境であることとファイルの書き込みができないからです。

プリコンパイル

ファイルの書き込みができない場合は、予めコンパイルしてクラスファイルを作っておくという手があります。

Scalateでは、テンプレートのソースコードを動的にコンパイルしJavaのクラスファイルを生成します。そして、そのクラスファイルをシステムの一時領域に保存します。GAEでは、ファイルの書き込みができないので、一時的にコンパイルしたクラスファイルを保存しておくことはできません。これを避ける為に、予めテンプレートをコンパイルしておく必要があるのです。Scalateでは、プリコンパイルされていればテンプレートのソースコードを再度コンパイルすることはないのでGAEでも大丈夫です。プリコンパイルするには、SBTのプラグインxsbt-scalate-generatorを使います。

SBTの設定
project/plugins.sbtに次のコードを追加します。
addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.4.2")

build.sbtに次のコードを追記します。
10 
11 
12 
13 
14 
15 
16 
seq(scalateSettings:_*)

scalateTemplateConfig in Compile <<= (sourceDirectory in Compile){ base =>
  Seq(
    TemplateConfig(
      // テンプレートの場所
      base / "webapp" / "WEB-INF"
      // 暗黙的にテンプレートにインポートします
      List("import scala.math._")
      // 暗黙的にテンプレートに変数を宣言します
      List(Binding("userAgent""String"))
      // 出力クラスのパッケージ
      Some("webTmpl") 
    )
  )
}

プリコンパイル
特別に何もする必要はありません。sbtのcompileコマンドやpackageコマンドでwarファイル作成時に自動的にテンプレートをコンパイルしてくれます。出力先は、targetディレクトリです。

サーブレット対応

サーブレット対応したintentメソッドは次のようになります。
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
    def intent = {
      // ResponseWriterを返すクロージャ
      case req @ Path(Seg( _ :: Nil )) => new ResponseWriter {
        def write( writer: OutputStreamWriter ) = {
          val engine = new TemplateEngine
          // サーブレットの環境を設定する
          engine.resourceLoader = new ServletResourceLoader(config.getServletContext)
          // build.sbtで指定したディレクトリ
          engine.workingDirectory = new File("WEB-INF")
          // build.sbtで指定したパッケージ
          engine.packagePrefix = "webTmpl"
          // layoutテンプレートの場所の設定
          engine.layoutStrategy = new DefaultLayoutStrategy(engine, TemplateEngine.templateTypes.map("WEB-INF/layouts/default." + _):_*)
          // プリコンパイルされている場合は、URIと同じ名前のクラスファイルを読み込む。
          // そうでない場合はテンプレートをコンパイルする。
          val scalateTemplate = engine.load(req.underlying.getServletPath)
          // 描画環境を設定
          val context = new DefaultRenderContext(req.underlying.getServletPath, engine, new PrintWriter(writer))
          // テンプレートを描画
          engine.layout( scalateTemplate, context )
        }
      }
    }

ちなみに、ServletTemplateEngineServletRenderContextとうクラスもあるので、こっちを使ってもできるかもしれませんが、試してません。

テンプレートファイルの作成

では、簡単なテンプレートを作成してみましょう。今回はScamlを使います。

レイアウトテンプレート
src/main/webapp/WEB-INF/layouts/default.scaml
-@ var body: String
%html
  %head
    %title Title
    %meta(http-equiv="Content-Type" content="text/html; charset=UTF-8")
  %body
    #{unescape(body)}

レイアウトテンプレートは、全てのテンプレートに自動的に適応されます。テンプレートの内容が#{unescape(body)}の部分に置換されます。

ページのテンプレート
src/main/webapp/index.scaml
%div Hello World

この場合は、%div Hello Worldがレイアウトテンプレートの#{unescape(body)}の部分に置換されます。

コンパイル

> sbt package

サーバを起動してブラウザで確認

開発用サーバを起動:
> appengine-java-sdk-***/bin/dev_appserver.cmd sbt/target/webapp

ブラウザでhttp://localhost:8080/index.scamlに行って、動作を確認してみましょう。ブラウザにHello Worldと表示されていれば成功です。

関連記事

GAE + SBT + Unfiltered


GAEをSBTで使う記事を書きましたが、今回はリクエストの処理にUnfilteredを使ってみることにしました。

Unfilteredとは

Unfilteredとは、いろんなフレームワークや環境に使えるリクエスト処理するライブラリらしい。Servletにも対応しているので、GAEでも使えるようです。

Servletに対応しているパッケージは、unfiltered-filterのようなので、これを使ってみましょう。

build.sbtの設定

build.sbtに次の記述を追加すれば、Unfilteredを使えるようになります。

libraryDependencies += "net.databinder" %% "unfiltered-filter" % "0.7.1"

フィルターの作成

リクエストを処理するフィルターを作成してweb.xmlに登録します。

Unfilteredでは、Planというトレイトを継承してフィルターを作るようです。

src/main/scala/mypackage/Filter.scalaを作成します。

10 
11 
12 
package mypackage {

  import unfiltered.request._
  import unfiltered.response._

  class Filter extends unfiltered.filter.Plan {
    def intent = {
      case Path(Seg( x :: Nil )) => ResponseString( x )
    }
  }

}

intentというメソッドでリクエストを処理します。caseでパス名を取得して、パス名をそのままページに表示するようにしました。

詳しい使い方は、こちらを参照してください。

web.xmlの設定

作ったフィルターをweb.xmlに登録します。

10 
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
  <filter>
    <filter-name>filter</filter-name>
    <filter-class>mypackage.Filter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

とりあえず、全てのリクエストはこのフィルターを通すように設定しました。

コンパイル

SBTのインタープリタに入り、packageと入力します。

> sbt
sbt> package

サーバの起動

appengine-java-sdk-***/bin/dev_appserver.cmd sbt/target/webapp
ブラウザでhttp://localhost:8080/hogeに行って、動作を確認してみましょう。ブラウザにhogeと表示されていれば成功です。

関連記事

2013年12月20日金曜日

Google App Engine + SBTで始めました

Google App EngineをSBTで使ってみました!

GAE用のSBTのプラグインsbt-appengineがあるようなので、これを使ってもいいのですが、GAE初心者の僕としては、GAEを理解する為に、sbt-appengineを使わずにやってみました。


Google App Engine SDK for Javaのダウンロード

ここからGoogle App Engine SDK for Javaをダウンロードします。

Google App Engine SDK for Javaの解凍

ダウンロードしたGoogle App Engine SDK for Javaを好きな場所に解凍します。

SBTのダウンロード

ここからZIPまたはTGZ版をダウンロードします。

SBTの解凍

ダウンロードしたSBTを好きな場所に解凍します。

xsbt-web-plugin

GAEはWARファイルを使ってデプロイするようなので、sbt-appengineは使いませんが、WARファイルを作る為のプラグインxsbt-web-pluginは使います。

plugins.sbtの設定

解凍したSBTディレクトリの中にsbt/project/plugins.sbtを作成します。

addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "0.5.0")

build.sbtの作成

解凍したSBTディレクトリの中にsbt/build.sbtを作成します。

10 
11 
12 
13 
name := "****"

organization := "****"

version := "0.1.0"

scalaVersion := "2.10.3"

seq(webSettings :_*)

libraryDependencies += "org.eclipse.jetty" % "jetty-webapp" % "9.1.0.v20131115" % "container"

libraryDependencies += "org.eclipse.jetty" % "jetty-plus" % "9.1.0.v20131115" % "container"

WARファイルに組み込むライブラリ

xsbt-web-pluginで作るWARファイルに入れるライブラリをsbt/libに置きます。必要なライブラリは、appengine-java-sdk-***/lib/userの中のjar全てです。サブディレクトリの中のjarもです。だだし、sbt/libにコピーするときに、全てのjarファイルをsbt/lib直下に置く必要があります。ディレクトリ構造をそのままコピーしなように注意が必要です。

外部ライブラリ参照

WARファイルに組み込まないライブラリも使用するので、それらのライブラリを参照するようにクラスパスの設定をする必要があります。

必要な外部ライブラリは、appengine-java-sdk-***/lib/sharedの中のライブラリです。

sbt/build.sbtに次の記述を追加します。

unmanagedJars in Compile ++= {
  val base = baseDirectory.value
  val gae = base / ".." / "appengine-java-sdk-1.8.8" / "lib" / "shared"
  val jars = (gae ** "*.jar") +++ (gae / "jsp" / "*.jar")
  jars.classpath
}

web.xmlの作成

sbt/src/main/webapp/WEB-INFweb.xmlを作成します。とりあえず、空のやつを作ります。

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
</web-app>

appengine-web.xmlの作成

sbt/src/main/webapp/WEB-INFappengine-web.xmlを作成します。とりあえず、最小限の項目を記述します。

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <application>application-id</application>
  <version>1</version>
  <threadsafe>true</threadsafe>
</appengine-web-app>

<application>タグに、アプリケーションIDを指定するのを忘れずに。アプリケーションIDは、ここから取得できます。

<threadsafe>タグは、アプリケーションが同時に複数のリクエストを受け付けられるようになっているかを指定する項目です。1つづしかリクエスト受け付けられなければfalse、同時に複数のリクエストを処理できる場合にはtrueを指定します。

静的ファイルでHello World

sbt/src/main/webappindex.htmlを作成ます。とりあえず、Hello Worldです。

<html>
  <head>
    <title>Title</title>
  </head>
  <body>
    Hello World
  </body>
</html>

WARファイルの作成

SBTのインタープリタに入り、packageと入力します。

> sbt
sbt> package

すると、sbt/targetにWARファイル化する前のwebappディレクトリが現れます。GAEの開発用ウェブサーバを起動するときには、このディレクトリを指定するといいでしょう。さらに、sbt/target/scala-***には、(project name)_(scala version)-(project version).warが作成されています。

開発用ウェブサーバの起動

開発用に使用するウェブサーバもGAE SDKについてきます。appengine-java-sdk-***/bin/dev_appserver.cmd(Windows)です。

起動
appengine-java-sdk-***/bin/dev_appserver.cmd sbt/target/webapp

起動したら、ブラウザでhttp://localhost:8080にアクセスしてみましょう。Hello Worldと表示されていれば成功です。

アプリケーションのアップロード

次のコマンドでアップロードができます。

appengine-java-sdk-***/bin/appcfg.cmd update sbt/target/webapp

途中でemailとpasswordを聞かれるので、入力します。しばらくすると完了します。

管理画面に行って、デプロイされている確認しましょう。

関連記事

2013年9月14日土曜日

Play Framework 2 のテンプレートでScalaの記述方法


基本的に次の3つだけ覚えていれば大抵のことはできる。
  • テンプレートの中でScalaのコードを埋め込むには'@{}'で囲む(Playの文法)
  • '@{}'の戻り値は、scala.xml.Elemやそのコレクションを返せばよい(Playの文法)
  • Scalaのxmlリテラルの中でScalaのコードを埋め込むには'{}'で囲む(これはScalaの文法)

例1
    @{
      for( x <- List(1, 2, 3, 4) if x%2 == 0 ) yield <div> {x*10} </div>
    }

@{}スコープの中では、Scalaの記法をすべて使えるので、変数や関数を定義してもよい。

例2
    @{
      val list = List(1, 2, 3, 4)
      for {
        x <- list
        if x%2 == 0
      } yield <div> {x*10} </div>
    }

ただし、別のスコープで定義した変数は使えない。

例3 これはダメ!
    @{
      val list = List(1, 2, 3, 4)
    }

    @{
      for {
        x <- list
        if x%2 == 0
      } yield <div> {x*10} </div>
    }

scala.xml.Elemやそのコレクションを返さなければならないので、foreachとかは使えない。

例4 これはダメ!
    @{
      List(1, 2, 3, 4).withFilter( _ % 2 == 0 ).foreach { x => <div> {x*10} </div> }
    }

mapを使えばよい

例5
    @{
      List(1, 2, 3, 4).withFilter( _ % 2 == 0 ).map { x => <div> {x*10} </div> }
    }

ちなみに、withFilterは、mapの中で評価されるので、ループが1度済む。
したがって、filterしてmapなどをするときは、withFilterを使ったほうがよい。
for式の中では、if式を使うと、このwithFilterが定義されていれば呼ばれ、なければfilterが呼ばれる。

その他にも規則はあるが、それはより簡単に記述するためのヘルパー的なものなので
覚えてなくても問題ない。

例えば、前述の例1を次のように書くことができる。

例6
    @for( x <- List(1, 2, 3, 4) if x%2 == 0 ) {
      <div> @(x*10) </div>
    }

例1との違いは'@{}'を使わずに'@'を使っているところ。
これは、Scalaを記述するスコープを定義するのではなくて、Scalaの記法や値を呼び出す為に使われる。
したがってScalaの記法や値を使う場所には、すべて'@'が必要。'x*10 -> @(x*10)'のように。

また、yieldを使っていないところも違う。
playが自動的にyieldを付加してくれるようだ。

次のようにも記述できる。

例7
    @List(1, 2, 3, 4).withFilter( _ % 2 == 0 ).map { x => <div> @{x*10} </div> }

このように、スコープを定義しなくていいので、少しだけ簡単に記述できる。

2012年2月10日金曜日

Liftでのプロパティファイル扱い

Liftのアプリケーションでいくつかのプロパティファイルを使い分けたい場合があります。
例えば、本番環境とテスト環境でDBの接続先を変えたい場合などです。
そうした場合、Liftは、プロパティファイルの名前によって使用するプロパティファイルを決めているようです。 基本的な、命名規則は、
modeName.userName.hostName.props
で、名前によって以下の順で優先的に読まれます。
/props/modeName.userName.hostName.props
/props/modeName.userName.props
/props/modeName.hostName.props
/props/modeName.default.props
/modeName.userName.hostName.props
/modeName.userName.props
/modeName.hostName.props
/modeName.default.props

nodeNameは、使用しているRun Modeのことで、例えば、"test"、"staging"、"production"、"profile"などを指定します。
"development"モードを使っている場合は、nodeNameは、省略してください。nodeNameに"development"って書いちゃうと、"development"モードとして正しく認識されないので注意が必要です。

userNameは、使っているOSのユーザ名にしなければならないようです。

 私の場合は、自分のローカルPCでは、リポジトリにあがっているプロパティファイルではなく 独自のプロパティファイルを使いたいので、自分の名前のプロパティファイルを作っています。
リポジトリにあがっているものは、本番で使うプロパティファイルで、 default.propsという名前にしてて、自分のPCでは、hogehoge.propsという名前にして、これはリポジトリの管理下においていません。

2011年12月30日金曜日

LiftでCassandraを使ってみる

LiftでCassandraを使ってみる


NoSQLのDBであるCassandraをScalaで試した時のメモ。


Cassandraのダウンロード


$> wget http://ftp.jaist.ac.jp/pub/apache//cassandra/1.0.6/apache-cassandra-1.0.6-bin.tar.gz


解凍


$> tar zxvf apache-cassandra-1.0.6-bin.tar.gz


サーバの起動
cassandraの設定はconf/cassandra.yamlで設定されていて、
デフォルトでは、単一モードで起動するようになっているようだ。
なので、今回は何も変更を加えずにそのまま起動。

$> cd apache-cassandra-1.0.6 
$> ./bin/cassandra -f 


管理者権限で実行する必要があるかも。


CLIの起動
CLIはcassandraをコマンドラインで操作する為のクライアント。


CLIのインタプリタに入る。
$> ./bin/cassandra-cli -h localhost 


キースペースを表示する。
[default@unknown] show keyspaces; 

行末にセミコロンを付けるのを忘れずに。


Cassandraのデータ構造
クラスター:Cassandraのインスタンス
キースペース:データベースのようなもの
カラムファミリー:テーブルのようなもの


キースペースは、たいていは1つのアプリに1つ定義されるものらしい。
Scalaから操作するとき用に、あらかじめキースペースを定義しといたほうがよい感じ。


Liftのダウンロード
Liftは、Scalaで書かれたウェブフレームワーク。
Liftには、Scalaのビルドツールのsbtが付いているので、
ウェブアプリを作らないという時でも、ビルドするのに便利。

$> wget https://github.com/lift/lift_24_sbt/tarball/master -O lift2.4.tar.gz


解凍
$> tar zxvf lift2.4.tar.gz

jettyの起動
今回は、関係ないけど、とりあえず、jettyを起動。

$> cd lift-lift_24_sbt-05be36f/scala_29/lift_basic/
$> ./sbt
$> update
$> jetty-run

ブラウザでhttp://localhost:8080が表示されてればOK。

Hectorの取得


mavenのように、project/build/LiftProject.scalaに以下の記述を追加する。
"me.prettyprint" % "hector-core" % "0.8.0-2"

sbtのupdateコマンドでhectorをとってくる。
$> ./sbt
$> update


HectorでColumnの作成と取得
src/main/scala/TestCassandra.scalaを作成して以下のように編集する。
以下のコードは、ほとんどCassandra本家のサイトのサンプルのコピー。

import me.prettyprint._
import cassandra._
import service._
import hector.api._
import factory._
import ddl._
import template._
import serializers._


import java.util._


object TestCassandra {


  def main( args:Array[String] ) {


    val clusterName = "Test Cluster" // クラスターの名前
    val replicationFactor = 1 // リプリケーションするノードの数
    val keyspaceName = "test_keyspace" // キースペースの名前
    val cfName = "test_cf3" // カラムファミリの名前


    // クラスターの取得。名前はcassandraのクラスター名と同じじゃなくてよい。
    val cluster = HFactory.getOrCreateCluster(clusterName, "localhost:9160")


    // カラムファミリの定義
    val cfDef = HFactory.createColumnFamilyDefinition(keyspaceName, cfName, ComparatorType.BYTESTYPE)


    // カラムファミリの追加。既にあれば、例外を投げる
    try {
      cluster.addColumnFamily(cfDef, true )
    } catch {
      // 例外処理
    }


    // キースペースの定義
    val keyspaceDef = HFactory.createKeyspaceDefinition(keyspaceName, ThriftKsDef.DEF_STRATEGY_CLASS, replicationFactor, Arrays.asList(cfDef))


    // キースペースの追加
    if( keyspaceDef == null ) cluster.addKeyspace( keyspaceDef, true )
    
    // キースペースインスタンスの取得
    val ksp = HFactory.createKeyspace(keyspaceName, cluster)


    // カラムの更新
    val template = new ThriftColumnFamilyTemplate[String, String](ksp, cfName, StringSerializer.get(), StringSerializer.get());
    val updater = template.createUpdater("a key");
    updater.setString("domain", "www.datastax.com");
    updater.setLong("time", System.currentTimeMillis());


    template.update(updater);


    // カラムの取得
    val res = template.queryColumns("a key");
    val value = res.getString("domain");


  }


}

実行
sbtのrunコマンドでコンパイルと実行をする。

$>./sbt
$> run

カラムの更新と取得ができてればOK。