技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

サービスデプロイのテスト環境を作るか

そろそろアプリをデプロイしようかと思ったのだけど、実はここで一つ問題が発生。

いや、問題と言うほどの問題ではないのかもしれないが、どうせ試行錯誤してセキュリティ的にアレな状態がそこそこ長く続きそうなので、仮想環境できちっと手段を確立してからいこうかと思った。

加えて言えば、人の手では実行速度に難があるので、Chef と Vagrant で手段をローカルに確立しようと思っただけ。

Ubuntu13.10 がホスト環境。

まずは vagrant, virtualbox の最新をインストール。

http://www.vagrantup.com/

https://www.virtualbox.org/

次は chef と chef solo を突っ込もう。

まずは Ruby だ。

sudo apt-get install build-essential bison libreadline6-dev curl git-core zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev autoconf libncurses5-dev -y

次は rbenv 突っ込む

$ cd
$ git clone git://github.com/sstephenson/rbenv.git .rbenv
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ vi ~/.bashrc

# add follow lines to tail
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

source コマンドで .bash_profile 読み込めばOK

Ruby の 1.9 系最新を突っ込む。

rbenv install --list で 1.9 系の最新を探す。とりあえずこのドキュメント執筆時に 1.8.3-p545 だったのでそれを入れる。

$ rbenv install 1.9.3-p545
$ rbenv rehash
$ rbenv global 1.9.3-p545
$ ruby --version
ruby 1.9.3p545 (2014-02-24 revision 45159) [x86_64-linux]

次は Chef か。

公式(http://www.getchef.com/)から落としてみる

$ sudo install bundler -y
$ gem update
$ curl -L https://www.opscode.com/chef/install.sh | sudo bash
$ knife configure

# 適当に yes マンしとけばいいよ

$ gem install knife-solo

Chef 使うなら berkshelf はないとやってられん。 ついでに omnibus 突っ込もう。

$ gem install berkshelf
$ vagrant plugin install vagrant-omnibus
$ vagrant plugin install vagrant-berkshelf --plugin-version 2.0.0.rc3
続きを読む

wkhtmltopdf を使ってみる

何者かと言うと、これ。

wkhtmltopdf

Webkit で指定したサイトをメモリ上でレンダリングして pdf とか画像に落とすライブラリ。

これを仮想マシンで構築した CentOS6 に突っ込んでみる。

マシンそのものは 6.5 x86_64 を指定。当然 64bit で、とりあえずは Desktop インストールです。

インストールが暇なのでサイトを眺めていたら、「Mac OS X builds could not be produced due to issues in QT 4.8.5 — you will have to use the latest development snapshot instead.」のメッセージを発見。

なるほど、QT は Webkit 搭載したよね確かに。

ビルドとかは必要なくて、とりあえず解凍すればいい。

で、いろいろ試してみた結果がこれ。

static なページ。

f:id:white-azalea:20140407221210p:plain

うん、問題なさそう。

作画ライブラリ系

f:id:white-azalea:20140407221322p:plain

動的に画面サイズが変わるとか耐えれない模様。

これは事前にサイズを指定するオプション食わせれば問題なさそう。

googlemap

動的な Ajax 作画画面はどうなるか?

f:id:white-azalea:20140407221502p:plain

動的表示部分は作画されなかった。

sencha

とにかく複雑な画面

f:id:white-azalea:20140407221552p:plain

そりゃまぁ無理か...

結論:Ajax を使わずに作画するならいける(Sencha は動的に何でも ajax しすぎてるからしょうがない)。

google_diff_match_patch

Google 先生で採用している Diff ライブラリ。

https://code.google.com/p/google-diff-match-patch/

jar 配布なしで、ソースでの提供です。Apache2 ライセンスなので、安心して使えますね。

各種言語で可能な限り同じ使い勝手を想定しているのか、命名規則が snake case だったりするのですが、まぁご愛嬌でしょう。

使ってみた

安心のコピペ可能ソースをぺたり。

  • Specs2
  • Scala2.10.x
  • sbt13.0 の環境で動作確認

注意、デフォルトパッケージ名からパッケージを移動してます。

後は知らん。

package utils.models

import org.specs2.mutable.Specification
import scala.collection.JavaConversions._


class DiffMatchPatch extends Specification {

  val base =
    """
      |# The first chapter.
      |Non editable line.
      |Updating first point.
      |The second edit point.
      |Conflicted edit point.
      |First chapter end.
    """.stripMargin

  val updateFirst =
    """
      |# The first chapter.
      |Non editable line.
      |Update first.
      |The second edit point.
      |Conflicted: updated by first changeset.
      |First chapter end.
      |
      |# The Second Chapter By First Change set.
      |
      |This message is created with first change set.
      |Is diff_match_patch's coding rule are best way?
    """.stripMargin

  "Diff match patch" should {
    "Get difference with updateFirst" in {
      val diff = new diff_match_patch()
      val lines = diff.diff_linesToChars(base, updateFirst)
      val (text1, text2, lineArray) = (lines.chars1, lines.chars2, lines.lineArray)
      val result = diff.diff_main(text1, text2, false)
      diff.diff_charsToLines(result, lineArray)

      result.foreach(v =>
        println("%s  %s".format(v.operation, v.text))
      )
      true
    }
  }
}

実行結果

EQUAL  
# The first chapter.
Non editable line.

DELETE  Updating first point.

INSERT  Update first.

EQUAL  The second edit point.

DELETE  Conflicted edit point.

INSERT  Conflicted: updated by first changeset.

EQUAL  First chapter end.

INSERT  
# The Second Chapter By First Change set.

This message is created with first change set.
Is diff_match_patch's coding rule are best way?

EQUAL      

尚、diff_linesToChars を使わずに呼び出すと、単語どころか、愚直に全部の差分を洗ってくるので注意が必要。

個人的に行比較だけ出来れば問題ないのでここはスルーしてみた。

Thymeleaf をPlayframework2.2.Xで無理やり使ってみた。

Java のテンプレートエンジン - 謎言語使いの徒然 でさくっと見たテンプレートを、じゃぁPlayframeworkでどうやるんだろうと、ドキュメント眺めながらやってみた。

で、まずはファイルが何処に配置されてるのかを返すクラスを作成。

package utils

import org.thymeleaf.resourceresolver.{FileResourceResolver, IResourceResolver}
import org.thymeleaf.TemplateProcessingParameters
import java.io.InputStream

class PlayThymeResourceResolver(viewPath: String) extends IResourceResolver {
  lazy val fileResourceResolver = new FileResourceResolver

  def getName: String = "PLAY_RESOURCE_RESOLVER"

  def getResourceAsStream(templateParameters: TemplateProcessingParameters, resourceName: String): InputStream = {
    this.fileResourceResolver.getResourceAsStream(templateParameters, s"$viewPath:$resourceName")
  }
}

それをテンプレートで食うようにして

package utils

import org.thymeleaf.templateresolver.TemplateResolver

class PlayThymeTemplateResolver(val viewLookupPath: String) extends TemplateResolver {
  setResourceResolver(new PlayThymeResourceResolver(viewLookupPath))
}

テンプレートエンジンでは多言語化対応と、Mapで突っ込めるようにしておく

package utils

import org.thymeleaf.{Arguments, TemplateEngine}
import org.thymeleaf.messageresolver.{MessageResolution, AbstractMessageResolver}
import play.api.i18n.Messages
import org.thymeleaf.context.Context
import scala.collection.JavaConversions._

class PlayTemplateEngine extends TemplateEngine {
  setMessageResolver(new AbstractMessageResolver {
    def resolveMessage(p1: Arguments, p2: String, p3: Array[AnyRef]): MessageResolution = {
      new MessageResolution(Messages(p2, p3))
    }
  })

  def process(path:String, variables: Map[String, AnyRef]):String = {
    val ctx = new Context()
    ctx.setVariables(variables)
    process(path, ctx)
  }
}

後はこれをシングルトンにでもしておけば

package utils

import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
import org.thymeleaf.resourceresolver.ClassLoaderResourceResolver
import org.thymeleaf.TemplateEngine
import play.api.Play

object PlayTemplateEngine {
  lazy val templateEngine = {
    val config = Play.current.configuration
    val engine = new PlayTemplateEngine

    val path   = config.getString("thymeleaf.path").getOrElse("app/views/")
    val suffix = config.getString("thymeleaf.suffix").getOrElse(".html")
    val mode   = config.getString("thymeleaf.mode").getOrElse("HTML5")
    val ttl    = config.getLong("thymeleaf.ttl").getOrElse(60000L) // default 1minute

    val templateResolver = new PlayThymeTemplateResolver(path)
    templateResolver.setSuffix(suffix)
    templateResolver.setTemplateMode(mode)
    templateResolver.setCacheTTLMs(ttl)
    templateResolver.setCharacterEncoding("UTF-8")

    engine.setTemplateResolver(templateResolver)
    engine
  }
}

Controller で呼べる

  def sample = Action { implicit request =>
    val ctx = new Context()
    ctx.setLocale(lang.toLocale)
    ctx.setVariable("name", "行くぜおい!")

    val res = Template.templateEngine.process("sample", ctx)
    Ok(res).as("text/html")
  }

そして、views にテンプレートを配置して出来上がり。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Test</title>
</head>
<body>
  <h1 th:text="${name}">Hello Sample</h1>
</body>
</html>

日本語化対応の扱いが Play と Thymeleaf で異なるので作ったメソッドがそのまま呼べない図。 ただ、レスポンスに「as」入れないと、text/plain で返ってしまうので、何か考えないとだめっぽい。

Java のテンプレートエンジン

今日 4 年ぶりにSpringを眺めてたら便利そうなのを発見。

http://www.thymeleaf.org/

Spring4 から正式に連携するようになったテンプレートエンジン。Spring 3 でも連携できる。 HTML/XHTML/HTML5 に特化して作られており、タグに属性の形で埋め込みを指定する。 その為、テンプレートが完全にHTMLとなるので、ブラウザなどで調整しながらデザインできるのが最大の利点。 ドキュメントを見たら、キャッシュや部分include等もあり、かなり使い勝手が良さそうだった。 Typesafe であることを捨てるなら、Playframeworkのテンプレートエンジンから乗り換えても良いかもしれない。

他のテンプレートエンジンもいくつかあったけど freemarker は特色なさそう。 ApacheVelocity も今さら感。 詳しくは見きれてないけど、どちらも似たもの同士に見えました。この二つは極論、Scaml Reference とか Jade Reference くらいはっちゃけて欲しかった。

Scala_curl で HTTP クライアントを作ってみる

このエントリは Scala Advent Calendar の 22 日目になります。

事の始まりは Playframework の WS を使いにくいと感じたこと。

multipart/form-data in WS POST

そもそも、WS で multipart を想定していないとのことでした。

そして、その下で動いてる AsyncHttpClient/async-http-client · GitHub でどうにか実現しようとしたのが Play2.2.1のWSがもう少しどうにかならんかと思って解析してみた - 謎言語使いの徒然 です。

それでもしんどいので、サードパーティのライブラリに走りました。

Scala で HTTP クライアントライブラリは、Dispatch が有名です。こちらは 2013 ScalaAdventCalendar で公開された 2013-12-18 - NetPenguinの日記 が参考になります。

で、まぁ DSL だらけになるのですね、これ。

ということで、次に使ってみたのが m3dev/curly · GitHub です。

こちらは Java/Scala 両対応で、非常にシンプルな IF をしているので、中々に使い勝手が良さそうでした。

最大の利点としては、ソケットに自前で書き込みをしているので、他の jar に依存しないことです。

これは複数のライブラリに依存しているプロジェクトでは非常に魅力的ではあります。

『だがいや待たれよ』

ソケットを自前で書いている以上、一部の操作が変更できないと言った内容や、送信で利用される一般的なヘッダが一部未実装であったり、multipart でエンコーディングが入ってないとか、数えていくと結構ありました。

手をくわえて pull request するのが筋かなとは思いましたが、Maven/SBT のハイブリッド構成や、SBT 側が Maven 側のデプロイに依存していたりと、手を加えてテストするには面倒な状態でした。

そっか、ならば

自分で使いたいもの作ればいいんだ

ということで作ってみました。

Sunao-Yoshii/scala_curl · GitHub

自分でソケットを書く気力はなかったので、ApacheCommons の御厄介にはなっていますが、なんとなく curly に似せて作ってます。

インストール

リポジトリは今日時点で作ってないので、当面は publishLocal となります。

  git clone https://github.com/Sunao-Yoshii/scala_curl.git
  sbt clean update publishLocal

あとは何時もの通り build.sbt に下記を書いて、設定終了です。

  libraryDependencies += "net.white-azalea" %% "scala_curl" % "0.1"

因みに Scala 2.10.3 でビルドしてます。

コード的に 2.10.x 系以降でのみビルドできるような状態です。

簡単な使い方

  import bet.azalea.curl._
  import net.azalea.curl.HTTPHelper._

  val response         = HTTP.get("http://localhost:8000")
  val status:Int       = response.status // returns server response code. like 200
  val body:Array[Byte] = response.bodyAsBytes // returns body content as byte array.
  val bodyStr:String   = response.bodyAsString()

PUT / POST も

  val putResponse  = HTTP.put("http://localhost/path/to", "SampleMessage".toEntity())
  val postResponse = HTTP.post("http://localhost/path/to", "SampleMessage".toEntity())

ファイルの送信等

  import org.apache.http.entity.mime.content.ContentBody

  val file = new java.io.File("sample.txt")
  HTTP.post("http://localhost/path/to", file.toEntity())

  // HTTP FORM 送信
  HTTP.post("http://localhost:9100/form", Map(
    "param1" -> "value1",
    "param2" -> "value2"
  ).toEntity)

  // multipart での送信
  HTTP.post("http://localhost:9100/multipart", Map[String, ContentBody](
    "param1" -> "value1".toContentBody(),
    "param2" -> "value2".toContentBody(),
    "file" -> file.toContentBody()
  ).toEntity)

という、今日の為に作りたてのライブラリです。

早速クライアントとして使ってみる

まずは build.sbt からです。

name := "curl_scala_sample"

version := "0.1"

scalaVersion := "2.10.3"

libraryDependencies ++= Seq(
  "net.white-azalea" %% "scala_curl" % "0.1"
)

そしてコード

object ReadMyFeed extends App {
  import net.azalea.curl._

  val rss = HTTP.get("http://white-azalea.hatenablog.jp/rss")
  println(rss.bodyAsString())
}

実行してみると、

> run
[info] Compiling 1 Scala source to D:\sources\sample\target\scala-2.10\classes...
[info] Running ReadMyFeed
<?xml version="1.0"?>
<rss version="2.0">
  <channel>
    <title>謎言語使いの徒然</title>
    <link>http://white-azalea.hatenablog.jp/</link>
    <description>適当に気になった技術や言語を流すブログ。</description>
    <lastBuildDate>Sat, 21 Dec 2013 22:39:45 +0900</lastBuildDate>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
(以下略)

実際に運用していくには全く枯れてないので、何とも不安は残りますが、興味本位で使って意見とか出していただけると幸いです。

頑張って github では英語を書いていますが、中身が日本人なので、日本語で質問でもリクエストでも書いていただいてもOKです。

Scala っぽいHTTPクライアント作ってみた

https://github.com/m3dev/curly

これの作りがすごいなーと思ったけど、

  1. 微妙に対応しきれてない類のものがあったの
  2. 自分で作ろうか
  3. パケットを自力で書くとか正気の沙汰じゃない!?
  4. Java 対応捨てて Scala で ApacheCommons 使って書くか

で作ったのがこれ。

作りかけなんだけどね。

https://github.com/Sunao-Yoshii/scala_curl

いえ、テストとか超適当なんですけどね?

殆ど ApacheCommonsHttpcomponent のラッパだから、基本送信部分だけ真面目にテストして、後はDSLで意図したインスタンスが生成できればいいかなー的な。

ドキュメントもめんどくさい。