Bacon.js で skip・take 処理

リアクティブプログラミング用ライブラリの Bacon.js を Node.js 上で使用し、「RxJS で行単位のファイル処理」 で試したような skip・take 処理のサンプルを実装してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20150104/

はじめに

npm でインストールするために下記のような package.json を用意します。 今回は CoffeeScript で実装するので coffee-script も追加しています。

package.json
{
  ・・・
  "dependencies": {
    "baconjs": "*",
    "coffee-script": "*"
  }
}

npm install でインストールします。

インストール
> npm install

Bus の利用

まずは Bus を使って、ストリームへ push した内容をそのままログ出力する処理を実装してみます。

Bus は後から要素を push できるストリームですが、Bacon.js には他にも色々とストリームを作る方法が用意されています。 (下記は一部)

  • 配列からストリームを作る Bacon.fromArray
  • コールバック関数を使ってストリームを作る Bacon.fromCallback, Bacon.fromNodeCallback (Node.js 用のコールバック関数へ適用)
  • 任意のストリームを作る Bacon.fromBinder
sample1.coffee
Bacon = require('baconjs').Bacon

bus = new Bacon.Bus()

bus.log()

[1..6].forEach (i) -> bus.push "sample#{i}"

bus.end()

実行結果は下記の通りです。 Bus へ push した sample1 ~ sample6 を順次出力しています。

実行結果
> coffee sample1.coffee

sample1
sample2
sample3
sample4
sample5
sample6
<end>

skip・take 処理

次に、下記のような加工処理を追加してみました。

  • (1) 2つの要素を無視 (skip)
  • (2) 3つの要素だけを取得 (take)
  • (3) 先頭に # を付ける (map)
sample2.coffee
Bacon = require('baconjs').Bacon

bus = new Bacon.Bus()

bus.skip(2).take(3).map((s) -> "##{s}").log()

[1..6].forEach (i) -> bus.push "sample#{i}"

bus.end()

実行結果は下記の通りです。

実行結果
> coffee sample2.coffee

#sample3
#sample4
#sample5
<end>

ジニ不純度の算出3 - Python, R, CoffeeScript

前々回前回 に続き、下記のようなプログラム言語でジニ不純度(ジニ係数)の算出処理を同様に実装してみました。

今回のソースは http://github.com/fits/try_samples/tree/master/blog/20140622/

Python で実装

Python では itertools モジュールの groupbycombinations 関数が使えます。

groupbyHaskell と同様に隣り合う同じ値をグルーピングできます。 (今回のケースでは sorted でソートが必要)

groupby 結果の値部分(グルーピング部分)には直接 len 関数を使えないようなので list 関数でリスト化してから len を適用します。

また、combinations 関数を使用すると Scala の combinations と同様に要素の組み合わせを取得できます。(下記では AB、AC、BC の 3種類)

gini.py
from itertools import *

def size(xs):
    return float(len(xs))

# (a) 1 - (AA + BB + CC)
def giniA(xs):
    return 1 - sum(map(lambda (k, g): (size(list(g)) / size(xs)) ** 2, groupby(sorted(xs))))

def countby(xs):
    return map(lambda (k, v): (k, size(list(v))), groupby(sorted(xs)))

# (b) AB * 2 + AC * 2 + BC * 2
def giniB(xs):
    return sum(map(
        lambda ((xk, xv), (yk, yv)): xv / size(xs) * yv / size(xs) * 2, 
        combinations(countby(xs), 2)
    ))

vlist = ["A", "B", "B", "C", "B", "A"]

print giniA(vlist)
print giniB(vlist)
実行結果
> python gini.py

0.611111111111
0.611111111111

Python 3 で実行するには

Python 3.4 で実行するにはラムダ式と print のところを書き換える必要があります。

Python 3 のラムダ式ではタプル内の複数の要素を個別に引数として取得できないようなので、Python 2 のように lambda (k, v): ・・・ とは書けず、lambda x: ・・・ として個々の要素をインデックスで参照 (x[0] 等) する事になります。

gini3.py (Python 3.4)
・・・
# (a) 1 - (AA + BB + CC)
def giniA(xs):
    return 1 - sum(map(lambda x: (size(list(x[1])) / size(xs)) ** 2, groupby(sorted(xs))))
・・・
# (b) AB * 2 + AC * 2 + BC * 2
def giniB(xs):
    return sum(map(
        lambda x: x[0][1] / size(xs) * x[1][1] / size(xs) * 2, 
        combinations(countby(xs), 2)
    ))

vlist = ["A", "B", "B", "C", "B", "A"]

print(giniA(vlist))
print(giniB(vlist))
実行結果 (Python 3.4)
> python gini3.py

0.6111111111111112
0.611111111111111

R で実装

R では table 関数で要素毎のカウント値を取得でき、combn 関数で ScalaPython の combinations と同様の組み合わせを行列(matrix)として取得できます。

lapply の結果(リスト)には sum 関数を適用できないようなので Reduce を使って合計しています。

また、apply の第2引数を 2 とすれば列単位にデータを処理できます。

gini.R
# (a) 1 - (AA + BB + CC)
giniA <- function(xs) {
  1 - Reduce("+", lapply(table(xs), function(x) (x / length(xs)) ^ 2))
}

# (b) AB * 2 + AC * 2 + BC * 2
giniB <- function(xs) {
  sum(apply(combn(table(xs), 2), 2, function(x) (x[1] / length(xs)) * (x[2] / length(xs)) * 2))
}

list <- c("A", "B", "B", "C", "B", "A")

giniA(list)
giniB(list)
実行結果
・・・
> giniA(list)
[1] 0.6111111

> giniB(list)
[1] 0.6111111

備考

各処理の結果は下記のようになります。

table(list) の結果
> table(list)

list
A B C 
2 3 1
combn(table(list), 2) の結果
> combn(table(list), 2)

     [,1] [,2] [,3]
[1,]    2    2    3
[2,]    3    1    1

ちなみに、上記は以下のような組み合わせのカウント値です。

     [,1] [,2] [,3]
[1,] "A"  "A"  "B" 
[2,] "B"  "C"  "C" 

CoffeeScript で実装

CoffeeScript では、Underscore.js 等のライブラリを使用しない限り、グルーピングや組み合わせの処理を自前で実装する事になると思います。

gini.coffee
countBy = (xs) -> xs.reduce (acc, x) ->
    acc[x] ?= 0
    acc[x]++
    acc
, {}

sum = (xs) -> xs.reduce (acc, x) -> acc + x

# (a) 1 - (AA + BB + CC)
giniA = (xs) -> 1 - sum( (v / xs.length) ** 2 for k, v of countBy(xs) )

flatten = (xs) -> xs.reduce (x, y) -> x.concat y

combination = (xs) -> flatten( [x, y] for x in xs when x isnt y for y in xs )

# (b) BA + CA + AB + CB + AC + BC
giniB = (xs) -> sum( (x[1] / xs.length) * (y[1] / xs.length) for [x, y] in combination([k, v] for k, v of countBy(xs)) )

list = ["A", "B", "B", "C", "B", "A"]

console.log giniA(list)
console.log giniB(list)
実行結果
> coffee gini.coffee

0.6111111111111112
0.611111111111111

Java Scripting API で CoffeeScript を実行

はじめに

Java SE 6 から実装されている Java Scripting API を使えば JavaScriptJVM 上で実行できます。そして、CoffeeScript は JavaScript にコンパイルできます。

と言う事で Java Scripting API を使って CoffeeScript を実行する Groovy スクリプトを作成してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20130618/

CoffeeScript から JavaScript への変換方法

今のところ Java Scripting API で直接 CoffeeScript を実行できませんので、まずは JavaScript へ変換する必要があります。

通常の CoffeeScript コンパイラは基本的に Node.js 用ですので、今回の用途には向いていないと思われます。

そこで、Web ブラウザ用の CoffeeScript コンパイラCoffeeScript ソース の下記ファイル)を使用する事にします。

  • extras/coffee-script.js

CoffeeScript を実行する Groovy スクリプト作成

coffee-script.js を使って CoffeeScript を実行する Groovy スクリプトは下記のようになりました。(安全な実行にはスコープ等を考えたほうが良いかもしれません)

run_coffee.groovy
import javax.script.*

// (1)
def engine = new ScriptEngineManager().getEngineByExtension('js')
// (2)
engine.eval(new FileReader('coffee-script.js'))
// (3)
engine.put('coffeeSrc', new File(args[0]).getText('UTF-8'))
// (4)
engine.eval('eval(CoffeeScript.compile(coffeeSrc))')

処理内容は単純で、JavaScript 用の ScriptEngine を取得し (1) 、coffee-script.js をロード(評価)します (2)。次に実行時引数で指定した CoffeeScript ソースファイルの内容を JavaScript 上の coffeeSrc 変数へ設定し (3)、最後に CoffeeScript.compile(coffeeSrc) で生成された JavaScript を eval で直接実行 (4) しています。

なお、(2) と (3) はどちらを先に実行しても支障は無いはずです。

単純な CoffeeScript の実行

それでは、下記のような単純な CoffeeScript ソースファイルを実行してみます。

sample.coffee
total = 0

total += value for value in [1..5]

println total

実行結果は下記の通り。一応動作しているようです。

実行結果
 > groovy run_coffee.groovy sample.coffee
 15

RxJava を利用した CoffeeScript の実行

次は、外部の JAR ファイルを用いた CoffeeScript ソースファイルを実行してみる事にします。 今回は fits:id:20130310 でも試した RxJava を使ってみました。

  • rxjava-core-0.9.0.jar
rx_sample.coffee
importClass java.util.Arrays
importPackage Packages.rx

Observable.from(Arrays.asList [1..10]).filter( (a) -> a % 2 is 0 ).skip(1).take(2).subscribe (a) -> println a

処理内容は、1 ~ 10 の数値から偶数を取り出し、先頭をスキップして 2 つのデータを取得して出力しています。

ここで、外部 JAR のパッケージやクラスを参照する場合は、Packages を使用する点に注意が必要です。

  • Packages.<パッケージ名>
  • Packages.<パッケージ名>.<クラス名>

上記 CoffeeScript を実行するには、Groovy スクリプトが rxjava-core-0.9.0.jar をロードできるように、ユーザーホームディレクトリの .groovy/lib へ rxjava-core-0.9.0.jar を配置する等しておきます。

実行結果
 > groovy run_coffee.groovy rx_sample.coffee
 4
 6

RxJS で行単位のファイル処理

前々回(id:fits:20130224)・前回(id:fits:20130310)のファイル処理と同様の処理を RxJS を使い CoffeeScript で実装してみました。

今回作成したソースは http://github.com/fits/try_samples/tree/master/blog/20130313/

RxJS インストール

RxJS をインストールするため下記のような package.json を用意して npm install しました。

package.json
{
  ・・・
  "dependencies": {
    "coffee-script": "*",
    "rxjs": "*"
  }
}
npm でインストール
> npm install
・・・
npm http GET https://registry.npmjs.org/rxjs
npm http GET https://registry.npmjs.org/coffee-script
・・・
rxjs@1.0.10621 node_modules\rxjs

coffee-script@1.6.1 node_modules\coffee-script

readFile を使ったサンプル

ファイルの内容を一括で読み込んでコールバック関数を呼び出す fs.readFile を使って実装してみました。

1行分のデータを PUSH する Observable はこれまでと同じように Observable.create を使って作成します。

ファイルの内容がコールバック関数の data に設定されてくるので、改行で split して 1行毎に onNext を呼び出します。

今回もキャンセル機能は用意しないため、何もしない関数 "->" を返しています。*1
ここで関数を返さないと実行時に Type Error が発生する事になります。

readline_file.coffee
rx = require 'rxjs'
fs = require 'fs'

fromFile = (file) ->
    rx.Observable.create (observer) ->
        fs.readFile file, (err, data) ->
            if err
                # エラー発生時
                observer.onError err
            else
                data.toString().split(/\n|\r\n/).forEach (line) ->
                    # 1行分のデータを PUSH
                    observer.onNext line
                # 完了時
                observer.onCompleted()

        # 何もしない関数を返す
        ->

# 1行目をスキップして 2・3 行目の先頭に # を付けて出力
fromFile(process.argv[2]).skip(1).take(2).subscribe (x) -> console.log '#' + x
実行結果
> coffee readline_file.coffee sample.txt
#サンプル
#

Stream を使ったサンプル

次は Stream を使って同様の処理を実装してみました。

fs.createReadStream で Stream を取得します。
ここで encoding を指定しておくと data イベントで取得できる内容が encoding で指定した文字コードの文字列となります。

Stream の場合は、基本的にバッファサイズでデータが分割される点に注意が必要で、適切な encoding を指定しておかないとバッファサイズによっては文字化けする事になります。

readline_file2.coffee
rx = require 'rxjs'
fs = require 'fs'

fromFile = (file) ->
    rx.Observable.create (observer) ->
        stream = fs.createReadStream file, {encoding: 'utf-8'}

        # エラー発生時
        stream.on 'error', (ex) -> observer.onError ex
        # 完了時
        stream.on 'end', -> observer.onCompleted()

        stream.on 'close', -> console.log '*** close'

        buf = ''

        stream.on 'data', (data) ->
            list = (buf + data).split(/\n|\r\n/)
            # 次に持ち越すデータ
            buf = list.pop()

            # 1行分ずつデータを PUSH
            observer.onNext line for line in list

        # 何もしない関数を返す
        ->

# 1行目をスキップして 2・3 行目の先頭に # を付けて出力
fromFile(process.argv[2]).skip(1).take(2).subscribe (x) -> console.log '#' + x
実行結果
> coffee readline_file2.coffee sample.txt
#サンプル
#
*** close

*1:Java の Observable.noOpSubscription() や C# の Disposable.Empty に該当

JavaScript で List モナド - Monadic

MonadicJavaScript 用のモナドライブラリです。

今回はこの Monadic を使って、以前 (id:fits:20120912, id:fits:20120930) に Scalaz や Functional Java で実装したナイト移動の List モナド処理 *1 を JavaScirpt で実装してみました。

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130120/

Monadic のインストール

npm install monadic でインストールするか、以下のように package.json の dependencies に monadic の設定を追加して npm install しておきます。

package.json
{
  "name": "monadic-list-sample",
  "version": "0.0.0",
  "dependencies": {
    "monadic": "*"
  }
}
実行例
> npm install

3手版

3手先のナイトの移動位置を全て列挙する処理と3手後に指定の終了位置に到達するか否かを判定する処理を実装します。

Monadic でバインド処理 *2 を行うには mbind を使います。List モナドは require('monadic').list() で取得できます。

3手版 move_knight.js
var monadic = require('monadic');
var listM = monadic.list();

// ナイトの次の移動先を列挙
var moveKnight = function(p) {
    return [
        {c: p.c + 2, r: p.r - 1}, {c: p.c + 2, r: p.r + 1},
        {c: p.c - 2, r: p.r - 1}, {c: p.c - 2, r: p.r + 1},
        {c: p.c + 1, r: p.r - 2}, {c: p.c + 1, r: p.r + 2},
        {c: p.c - 1, r: p.r - 2}, {c: p.c - 1, r: p.r + 2}
    ].filter(function(t) {
        return (1 <= t.c && t.c <= 8 && 1 <= t.r && t.r <= 8);
    });
};

// 3手先の移動位置を列挙(重複あり)
var in3 = function(start) {
    return listM.mbind(
        listM.mbind(
            // 下記の listM.mbind(・・・) は moveKnight(start) でも可
            listM.mbind(
                [start], 
                moveKnight
            ),
            moveKnight
        ),
        moveKnight
    );
};

console.log(in3({c: 6, r: 2}));

// 指定位置に3手で到達できるか否かを判定
var canReachIn3 = function(start, end) {
    return in3(start).some(function(p) {
        return (p.c == end.c && p.r == end.r);
    });
};

console.log(canReachIn3({c: 6, r: 2}, {c: 6, r: 1}));
console.log(canReachIn3({c: 6, r: 2}, {c: 7, r: 3}));

実行結果は以下の通り。

3手版の実行結果
> node move_knight.js
[ { c: 8, r: 1 },
  { c: 8, r: 3 },
  { c: 4, r: 1 },
  { c: 4, r: 3 },
  ・・・
  { c: 3, r: 4 },
  { c: 3, r: 8 } ]
true
false

do 記法 3手版

上記サンプルの in3 の処理を Monadic の do 記法を使って実装し直したのが下記です。
Monadic の do 記法は以下のように実装します。(Haskell の do に近い記述が可能になっています)

  • do の後にモナドオブジェクト(下記の listM) *3 を指定する
  • 変数 <- 関数(・・・) で関数の結果を変数に束縛
  • return でモナド処理の結果を返す
do 記法 3手版 move_knight.mjs
var monadic = require('monadic');
var listM = monadic.list();

var moveKnight = function(p) {
    ・・・
};

var in3 = function(start) {
    // do 記法で実装
    return do listM {
        first  <- moveKnight(start)
        second <- moveKnight(first)
        third  <- moveKnight(second)

        return third
    };
};

console.log(in3({c: 6, r: 2}));

var canReachIn3 = function(start, end) {
    ・・・
};

console.log(canReachIn3({c: 6, r: 2}, {c: 6, r: 1}));
console.log(canReachIn3({c: 6, r: 2}, {c: 7, r: 3}));

do 記法を使った .mjs ファイルのオーソドックスな実行方法が分からなかったので、-e オプションを使って実行してみました。

require('monadic').transform() で 'move_knight.mjs' を純粋な JavaScript コードへ変換し eval 実行しています。

do 記法 3手版の実行結果
> node -e "eval(require('monadic').transform(process.argv[1]))" move_knight.mjs
[ { c: 8, r: 1 },
  { c: 8, r: 3 },
  { c: 4, r: 1 },
  { c: 4, r: 3 },
  ・・・
  { c: 3, r: 4 },
  { c: 3, r: 8 } ]
true
false

transform による変換結果は以下のようになります。

require('monadic').transform('move_knight.mjs') の変換結果(do の箇所)
・・・
var in3 = function (start) {
    return listM.mbind(moveKnight(start), function (first) {
        return listM.mbind(moveKnight(first), function (second) {
            return listM.mbind(moveKnight(second), function (third) {
                return function () {
                    return listM.mreturn(third);
                }.bind(this)();
            }.bind(this));
        }.bind(this));
    }.bind(this));
};
・・・

N手版

N手版は、moveKnight を使ったバインド処理 (listM.mbind) を入れ子に N個連結するため reduceRight を使いました。

N手版 move_knight_many.js
var monadic = require('monadic');
var listM = monadic.list();

var moveKnight = function(p) {
    ・・・
};

// N手 (num) 先の移動位置を列挙(重複あり)
var inMany = function(num, start) {
    // num 個の moveKnight から成る配列を作成
    var items = new Array(num);
    for (var i = 0; i < num; i++) {
        items.push(moveKnight);
    }
    // num 個の moveKnight を listM.mbind で連結
    return items.reduceRight(
        function(acc, elem) {
            return listM.mbind(acc, elem);
        }, 
        [start]
    );
};

console.log(inMany(3, {c: 6, r: 2}));

// 指定位置に N手(num) で到達できるか否かを判定
var canReachInMany = function(num, start, end) {
    return inMany(num, start).some(function(p) {
        return (p.c == end.c && p.r == end.r);
    });
};

console.log(canReachInMany(3, {c: 6, r: 2}, {c: 6, r: 1}));
console.log(canReachInMany(3, {c: 6, r: 2}, {c: 7, r: 3}));

実行結果は以下の通り。

N手版の実行結果
> node move_knight_many.js
[ { c: 8, r: 1 },
  { c: 8, r: 3 },
  { c: 4, r: 1 },
  { c: 4, r: 3 },
  ・・・
  { c: 3, r: 4 },
  { c: 3, r: 8 } ]
true
false

CoffeeScript 版

最後に、3手版とN手版のサンプルを CoffeeScript で実装し直してみました。

  • CoffeeScript 1.4.0

Monadic の do 記法に関しては、TameJS の時 (id:fits:20120415) と同様に Embedded JavaScript を使えばいけるかもしれませんが今回は調査していません。

CoffeeScript 3手版 move_knight.coffee
monadic = require 'monadic'
listM = monadic.list()

moveKnight = (p) ->
    [
        {c: p.c + 2, r: p.r - 1}, {c: p.c + 2, r: p.r + 1},
        {c: p.c - 2, r: p.r - 1}, {c: p.c - 2, r: p.r + 1},
        {c: p.c + 1, r: p.r - 2}, {c: p.c + 1, r: p.r + 2},
        {c: p.c - 1, r: p.r - 2}, {c: p.c - 1, r: p.r + 2}
    ].filter (t) -> t.c in [1..8] and t.r in [1..8]

in3 = (start) ->
    listM.mbind(
        listM.mbind(
            listM.mbind(
                [start], 
                moveKnight
            ),
            moveKnight
        ),
        moveKnight
    )

console.log in3 {c: 6, r: 2}

canReachIn3 = (start, end) ->
    in3(start).some (p) ->
        p.c == end.c and p.r == end.r

console.log canReachIn3 {c: 6, r: 2}, {c: 6, r: 1}
console.log canReachIn3 {c: 6, r: 2}, {c: 7, r: 3}
CoffeeScript 3手版の実行結果
> coffee move_knight.coffee
[ { c: 8, r: 1 },
  { c: 8, r: 3 },
  { c: 4, r: 1 },
  { c: 4, r: 3 },
  ・・・
  { c: 3, r: 4 },
  { c: 3, r: 8 } ]
true
false

N手版は以下の通りです。
[1..num].map( -> moveKnight ) で num 個の moveKnight から成る配列を作っています。

CoffeeScript N手版 move_knight_many.coffee
monadic = require 'monadic'
listM = monadic.list()

moveKnight = (p) ->
    [
        {c: p.c + 2, r: p.r - 1}, {c: p.c + 2, r: p.r + 1},
        {c: p.c - 2, r: p.r - 1}, {c: p.c - 2, r: p.r + 1},
        {c: p.c + 1, r: p.r - 2}, {c: p.c + 1, r: p.r + 2},
        {c: p.c - 1, r: p.r - 2}, {c: p.c - 1, r: p.r + 2}
    ].filter (t) -> t.c in [1..8] and t.r in [1..8]

inMany = (num, start) ->
    [1..num].map( -> moveKnight ).reduceRight (acc, elem) ->
        listM.mbind(acc, elem)
    , [start]

console.log inMany 3, {c: 6, r: 2}

canReachInMany = (num, start, end) ->
    inMany(num, start).some (p) ->
        p.c == end.c and p.r == end.r

console.log canReachInMany 3, {c: 6, r: 2}, {c: 6, r: 1}
console.log canReachInMany 3, {c: 6, r: 2}, {c: 7, r: 3}
CoffeeScript N手版の実行結果
> coffee move_knight_many.coffee
[ { c: 8, r: 1 },
  { c: 8, r: 3 },
  { c: 4, r: 1 },
  { c: 4, r: 3 },
  ・・・
  { c: 3, r: 4 },
  { c: 3, r: 8 } ]
true
false

*1:チェス盤のナイトの現在位置から次に移動可能な位置を列挙する

*2:Haskell や Scalaz の >>= に該当

*3:モナドオブジェクトには mreturn と mbind メソッドが必要

Vert.x で WebSocket

Vert.x 1.3.1 Final で単純な WebSocket 処理を実装してみました。

単純な JSON データを送受信するチャットサーバーを Vert.x で、動作確認のためのクライアントを HTML5 で実装する事にします。

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130114/

クライアントの作成 (HTML5

まずはクライアントです。

以下のような JSON データを送受信する WebSocket の処理を CoffeeScript で実装しました。送信する画像データは Web ブラウザへ画像ファイルをドラッグアンドドロップする事で切り替えられるようにしています。

{"message": <メッセージ>, "image": <Data URL 形式の画像データ>}
index.coffee
$( -> 
    # ドラッグアンドドロップ処理
    addEventListener 'dragover', (ev) ->
        ev.preventDefault()
    ,false

    # ドロップした画像で img の src を置き換える処理
    addEventListener 'drop', (ev) ->
        ev.preventDefault()

        file = ev.dataTransfer.files[0]

        if file.type.indexOf('image/') is 0
            r = new FileReader()
            r.onload = (ev) -> $('#image').attr 'src', ev.target.result
            r.readAsDataURL file

    # WebSocket
    ws = new WebSocket 'ws://localhost:8080/connect'

    # JSONデータ受信時の処理
    ws.onmessage = (event) ->
        obj = JSON.parse event.data
        $('#list').prepend "<div><img class=\"chatimg\" src=\"#{obj.image}\" /><p class=\"msg\">#{obj.message}</p></div>"

    # 接続時の処理
    ws.onopen = (event) -> console.log "open : #{event}"
    # 切断時の処理
    ws.onclose = (event) -> console.log "close : code = #{event.code}, reason = #{event.reason}"

    # JSONデータ送信処理
    $('#sendMessage').click ->
        msgEl = $('#message')

        params =
            message: msgEl.val()
            image: $('#image').attr('src')

        ws.send JSON.stringify(params)
        msgEl.val('')

    # ページ終了時の処理
    $(window).bind 'beforeunload', ->
        ws.close()
)

上記ファイルを JavaScript へ変換しておきます。(下記コマンドで index.js ファイルが生成されます)

JavaScript への変換
> coffee -c -b index.coffee

HTML ファイルの内容は以下の通りです。

index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>image chat sample</title>
    <link rel="stylesheet" href="index.css" />
    <script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
    <script src="index.js"></script>
</head>
<body>
    <div>
        <img id="image" src="data:image/png;base64,iVBORw・・・Jggg==" class="chatimg" />
        <textarea id="message" cols="50" rows="2" ></textarea>
        <br />
        <button id="sendMessage">send</button>
    </div>

    <hr />

    <div id="list"></div>
</body>
</html>

サーバーの作成 (Vert.x)

それでは、本題のサーバー部分を Groovy で実装してみます。

Vert.x の Groovy では、HttpServer の websocketHandler へクロージャを渡して WebSocket の処理を実装します。

このクロージャは新規クライアントからの接続が発生する度に実行され、パラメータとして WebSocket クラスのインスタンスが渡ってきます。

WebSocket クラスには複数のハンドラーメソッド(xxxHandler)が用意されており、任意のハンドラーにクロージャを渡して各処理を実装します。

今回は以下のハンドラーを使いました。

  • dataHandler でデータ受信時の処理を実装 (全クライアントへデータ送信)
  • closedHandler でクライアントとの接続が切れた際の処理を実装 (wsMap から接続が切れたものを削除)

下記サンプルでは、ConcurrentHashMap で WebSocket のインスタンスを管理しておき、JSON 受信時に全 WebSocket インスタンスへ JSON をそのまま送信するよう実装しています。

server.groovy
def wsMap = [:] as java.util.concurrent.ConcurrentHashMap

vertx.createHttpServer().websocketHandler { ws ->
    ws.dataHandler { data ->
        // 全クライアントへデータ送信
        wsMap.each { k, v ->
            v.writeTextFrame(data.toString())
        }
    }

    ws.closedHandler {
        // 接続切断されたものを wsMap から削除
        wsMap.remove(ws.textHandlerID)
        println "closed : ${ws.textHandlerID}"
    }

    println "connect : ${ws.textHandlerID}"
    wsMap.put(ws.textHandlerID, ws)

}.listen 8080

println "server started ..."

下記のように vertx run でサーバーを起動した後、index.html ファイルを複数の Web ブラウザで開いて *1 send ボタンを押下すると JSON の送受信を確認できると思います。

ただし、一度 JSON データを送信した後でないと受信できないようなので注意。(原因はよく分かってません)

実行例
> vertx run server.groovy
server started ...
connect : 8680eeb8-4666-4ec0-9496-98d8269f8099
connect : 1dd620bb-f9b3-4bed-90c9-aa2bb2243786
closed : 8680eeb8-4666-4ec0-9496-98d8269f8099
・・・
画面例

*1:今回はローカルファイルを直接開きました

軽量 Web フレームワークで REST API を実装 - Vert.x, Gretty, Play2 Mini, Socko, Restify

個人的に REST API の実装では JAX-RSJava*1SinatraRuby) あたりを使っていますが、今回は選択肢を増やす目的で下記のようなフレームワークを試してみました。

ちなみに、今回試した Java 系のフレームワーク(Restify 以外)は内部的に Netty を使っています。

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130106/

はじめに

今回は以下のような単純な REST API を実装する事にします。

  • /user/ で JSON 文字列 を GET
  • /user で JSON 文字列 を POST

動作確認は、以下の Ruby スクリプトで行う事にします。

client.rb
#coding:utf-8
require 'net/http'
require 'json/pure'

Net::HTTP.start('localhost', 8080) { |http|
    # GET 処理
    res = http.get('/user/1')
    puts "#{res}, #{res.code}, #{res.content_type}, #{res.body}"

    data = {
        'name' => 'test',
        'note' => 'サンプル'
    }
    # POST 処理
    res = http.post('/user', JSON.generate(data), {'Content-Type' => 'application/json'})
    puts "#{res}, #{res.code}, #{res.body}"
}

Vert.x

  • Vert.x 1.3.0

id:fits:20120513 や id:fits:20120708 で扱った Vert.x です。

Vert.x は JVM 用の Node.js ライクなサーバーフレームワークで、JavaJavaScript・CoffeeScript・Groovy・RubyPython と多様な言語をサポートしています。

RouteMatcher を使えば Sinatra のように HTTP Method と URL パターンの組み合わせで処理を実装できます。

下記サンプルは Groovy で実装しました。

vert.x/server.groovy
import org.vertx.groovy.core.http.RouteMatcher
import org.vertx.java.core.json.impl.Json

def rm = new RouteMatcher()

rm.get '/user/:id', { req ->
    def res = req.response

    res.putHeader('Content-Type', 'application/json')
    res.end Json.encode([
        id: req.params['id'],
        name: 'vert.x sample'
    ])
}

rm.post '/user', { req ->
    req.bodyHandler {
        // JSON を Map へデコード
        def data = Json.decodeValue(it.toString(), Map)
        println data

        req.response.end()
    }
}

vertx.createHttpServer().requestHandler(rm.asClosure()).listen 8080

println "server started ..."
実行と動作確認結果(サーバー側)
> vertx run server.groovy
server started ...
[name:test, note:サンプル]
動作確認結果(クライアント側)
> jruby client.rb
#<Net::HTTPOK:0x889b125>, 200, application/json, {"id":"1","name":"vert.x sample"}
#<Net::HTTPOK:0x7ff843da>, 200,

Gretty

  • Gretty 0.4.302

Gretty は Netty をベースにしたサーバーフレームワークJava・Groovy・Scala をサポートしています。

下記サンプルは Groovy++ で実装していますが、Groovy++ は今のところ Groovy 2.0 をサポートしていないようで、Groovy 1.8 で実行する必要がありました。

gretty/server.groovy
/*
 *  Groovy 1.8 で実行する必要あり
 *  Groovy 2.0 では ExceptionInitilizerError が発生
 */
@GrabResolver(name = 'gretty', root = 'http://groovypp.artifactoryonline.com/groovypp/libs-releases-local/')
@Grab('org.mbte.groovypp:gretty:0.4.302')
import static java.nio.charset.StandardCharsets.*

import static org.mbte.gretty.JacksonCategory.*
import org.mbte.gretty.httpserver.GrettyServer

GrettyServer server = []

server.groovy = [
    localAddress: new InetSocketAddress('localhost', 8080),
    '/user/:id': {
        get {
            response.json = [
                id: request.parameters['id'],
                name: 'gretty sample'
            ]
        }
    },
    '/user': {
        post {
            /*
             * request.contentText を使うとプラットフォームの
             * デフォルトエンコードが使われるようなので
             * 明示的に UTF-8 で処理
             */
            def data = fromJson(Map, request.content.toString(UTF_8))
            println data

            response.json = ''
        }
    }
]

server.start()
実行と動作確認結果(サーバー側)
> groovy server.groovy
1 06, 2013 10:25:00 午前 org.mbte.gretty.AbstractServer
情報: Started server on localhost/127.0.0.1:8080
[name:test, note:サンプル]
動作確認結果(クライアント側)
> jruby client.rb
#<Net::HTTPOK:0x12da7d5f>, 200, application/json, {"id":"1","name":"gretty sample"}
#<Net::HTTPOK:0x889b125>, 200,

Play2 Mini

  • Play2 Mini 2.0.3

Play2 Mini は Play2 をベースにした簡易フレームワークで、JavaScala をサポートしています。

Scala で URL パスを正規表現マッチさせるには Through を使います。 *2
Through は引数の種類によってブロックに渡る引数の内容(下記サンプルの groups: List[String])が異なります。

Throughの引数 ブロックの引数 Throughの引数例 URL例 ブロックの引数例(List[String])
正規表現 正規表現のグループ "/user/(.*)".r /user/a/b ["a/b"]
文字列 指定の文字列以降を "/" でスプリットしたもの "/user" /user/a/b ["a", "b"]

なお、Play2 Mini では以下のようにして実装します。

  • (1) com.typesafe.play.mini.Application トレイトを extends して route を実装
  • (2) (1) を指定した com.typesafe.play.mini.Setup を extends したグローバルパッケージの Global を作成
  • (3) play.core.server.NettyServer で実行
play-mini/Server.scala (1)
package fits.sample

import com.typesafe.play.mini._
import play.api.mvc._
import play.api.mvc.Results._
import play.api.libs.json._

object Server extends Application {
    def route = Routes(
        Through("/user/([^/]*)".r) { groups: List[String] =>
            Action {
                val id :: Nil = groups

                Ok(Json.toJson {
                    Map(
                        "id" -> id,
                        "name" -> "play-mini sample"
                    )
                })
            }
        },
        {
            case POST(Path("/user")) => Action { req =>
                val data = req.body.asJson
                data.foreach(println)

                Ok("")
            }
        }
    )
}
play-mini/Global.scala (2)
object Global extends com.typesafe.play.mini.Setup(fits.sample.Server)
play-mini/build.sbt (3)
scalaVersion := "2.9.2"

resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"

libraryDependencies += "com.typesafe" %% "play-mini" % "2.0.3"

mainClass in (Compile, run) := Some("play.core.server.NettyServer")

デフォルトのポート番号 9000 ではなく 8080 で実行するため、sbt run 時に -Dhttp.port=8080 を指定します。

実行と動作確認結果(サーバー側)
> sbt -Dhttp.port=8080 run
・・・
[info] play - Listening for HTTP on port 8080...
{"name":"test","note":"サンプル"}
動作確認結果(クライアント側)
> jruby client.rb
#<Net::HTTPOK:0x7ff843da>, 200, application/json, {"id":"1","name":"play-mini sample"}
#<Net::HTTPOK:0x1d7ae341>, 200,

Socko

  • Socko 0.2.3

Socko は Netty と Akka をベースとした Web サーバーフレームワークです。
Akka の Actor として処理を実装するので、多少コード量が多くなります。

socko/Server.scala
package fits.sample

import scala.util.parsing.json.{JSON, JSONObject}

import org.mashupbots.socko.events.HttpRequestEvent
import org.mashupbots.socko.routes.{Routes, GET, POST}
import org.mashupbots.socko.webserver.{WebServer, WebServerConfig}

import akka.actor.{Actor, ActorSystem, Props}

object Server extends App {
    class UserGetHandler extends Actor {
        def receive = {
            case req: HttpRequestEvent =>
                val path = req.request.endPoint.path
                val id = path.replace("/user/", "").split("/").head

                req.response.write(
                    JSONObject(
                        Map(
                            "id" -> id,
                            "name" -> "socko sample"
                        )
                    ).toString(), 
                    "application/json"
                )
                context.stop(self)
        }
    }

    class UserPostHandler extends Actor {
        def receive = {
            case req: HttpRequestEvent =>
                val content = req.request.content.toString
                val data = JSON.parseFull(content)

                data.foreach(println)

                req.response.write("")
                context.stop(self)
        }
    }

    val actorSystem = ActorSystem("SampleActorSystem")

    val routes = Routes({
        case GET(req) => actorSystem.actorOf(Props[UserGetHandler]) ! req

        case POST(req) if req.endPoint.path == "/user" =>
            actorSystem.actorOf(Props[UserPostHandler]) ! req
    })

    val server = new WebServer(WebServerConfig(port = 8080), routes, actorSystem)
    server.start()

    Runtime.getRuntime.addShutdownHook(new Thread() {
        override def run { server.stop() }
    })
}
socko/build.sbt
scalaVersion := "2.9.2"

resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"

libraryDependencies += "org.mashupbots.socko" %% "socko-webserver" % "0.2.3"

mainClass in (Compile, run) := Some("fits.sample.Server")
実行と動作確認結果(サーバー側)
> sbt run
・・・
[info] Running fits.sample.Server
11:14:00.300 [run-main] INFO  o.m.socko.webserver.WebServer - Socko server 'WebServer' started on localhost:8080
11:14:21.032 [New I/O  worker #1] DEBUG o.m.socko.webserver.RequestHandler - HTTP EndPoint(GET,localhost:8080,/user/1) CHANNEL=-384208271
11:14:21.098 [New I/O  worker #1] DEBUG o.m.socko.webserver.RequestHandler - HTTP EndPoint(POST,localhost:8080,/user) CHANNEL=-384208271
Map(name -> test, note -> サンプル)
動作確認結果(クライアント側)
> jruby client.rb
#<Net::HTTPOK:0x7ff843da>, 200, application/json, {"id" : "1", "name" : "socko sample"}
#<Net::HTTPOK:0x6eddcf85>, 200,

Restify

  • Restify 1.4.2

Restify は Express によく似た Node.js 用のフレームワークで、REST API の実装に特化しています。

最新バージョンは Restify 2.0.4 でしたが、Windows OS 上の npm install に失敗するので、今回は古いバージョンを使っています。

app.coffee
restify = require 'restify'

server = restify.createServer()

server.use restify.bodyParser()

server.get '/user/:id', (req, res, next) ->
    res.json
        id: req.params.id
        name: 'restify sample'

    next()

server.post '/user', (req, res, next) ->
    data = JSON.parse req.body
    console.log data

    res.json null
    next()

server.listen 8080, -> console.log "server started ..."
実行と動作確認結果(サーバー側)
> coffee app.coffee
server started ...
{ name: 'test', note: 'サンプル' }
動作確認結果(クライアント側)
> jruby client.rb
#<Net::HTTPOK:0x14980563>, 200, application/json, {"id":"1","name":"restify sample"}
#<Net::HTTPOK:0x42eded9>, 200,

*1:Jersey, RESTEasy 等

*2:Java で実装する場合は JAX-RS と同様にアノテーション(@URL)で URL のパスを指定するようです