久々にCommon Lisp

久々にCommon Lispネタ。

最近は色々やりたいことが増えてきて困る。 今週末、JavaScript(JQuery)を使って、WEB UIを作ってみたいなーと思い、 サーバサイドは何で作ろうかなーと考えてみた。

今、Erlangで作ってる監視アプリで、DBの値をJSONで返すAPIを作って、JavaScriptからその APIを呼び出してUIに表示するってのもやりたかったのですが、久々にLispを触ってみたくなった。

てことで、まずはCommon Lispのおさらいから。 「実践 Common Lisp」の最初のほうから読み直してみる。

第3章の「簡単なデータベース」を読んでいて、ピンときた。 これってパスワード管理ツールとして使えるなーと。そして、WEB UI持たせたら便利かもと。

で、「簡単なデータベース」のソースコードをほんの少し変えてみて、パスワード管理ツールに してみた。 ソースコードは以下。(ほとんど手を加えていません)

初回の使い方としては、以下を実行。 (add-system)関数を実行すると入力プロンプトが出てくるので、それに従えばOK。

CL-USER> (load "id-manage.lisp")
CL-USER> (add-system)

一旦入力内容をファイルに書き出し(忘れるといけないので)

CL-USER> (save-db "id.db")

次に、入力した内容を表示してみる。 例えば、(add-system)でsystem名としてgoogleを登録していた場合、以下のようにして 内容を検索。CREATEは登録日時として、自動で入力されるようになっている。

CL-USER> (select (where :system "google"))
SYSTEM:   google
ID:       example@example.com
PASSWORD: example
CREATE:   Sunday, May 13, 2012
UPDATE:   NIL

以前に入力した内容を更新する場合は以下。 system名がgoogleのレコードに対して、passwordエントリの内容をhogehogeに更新

CL-USER> (update-record (where :system "google") :password "hogehoge")

再度検索してみると、ちゃんと変ってる。

CL-USER> (select-record (where :system "google"))
SYSTEM:   google
ID:       example@example.com
PASSWORD: hogehoge
CREATE:   Sunday, May 13, 2012
UPDATE:   NIL

CL-USER> (save-db "id.db")

あとは、deleteなんかも付けたけど、使いかたはselect-recordと同じ。 2回目以降はREPLを起動したときに、(load-db “id.db”)として、保存した内容を読み込むのを忘れないように。

と、WEB UIを作る話しからかなり横道に入ってしまいましたが、次回はこれをWEB UIに表示させてみたいと思います。 ちょこっと調べたりしてたけど、今日は時間切れ。 @nitoro_idiotさんことfukamachiさんが作っている、Cavemanが良さそうなので、もう少し勉強して使ってみようと思う。

YawsでREST

前回、BIFのhttpモジュールを使ってRESTなWEBアプリが作れるかもと書いたんですが、 色々試した結果、割と面倒なことが判明。 URIを取得して、それに応じた処理は書けるんですが、リソースの場所までのルーティングの 定義がわからなかった。

で、Yawsなら簡単にできそうだったので、ここ を参考にしてちょっと試してみる。

やりたいことを実現するには、yaws.confのserverセクション内でappmodを定義すればよい。 appmod = <パス, モジュール名> のような仕様となっており、HTTPのリクエストが「パス」に合致した場合には「モジュール名」を呼びだすことができる。 よって、BIFのhttpモジュールでは面倒であった、ルーティングが簡単に定義できるっぽい。

ということでyaws.confに以下を追加。 意図としてはhttp://SERVER_NAME/foo/…. というリクエストがきた場合にはfooを呼び出す。

 
        port = 10080
        listen = 0.0.0.0
        docroot = /Users/hiyosi/Code/Erlang/work/rest_server/htdocs
        auth_log = true
        appmods =  # ココを追加
 

呼び出されるfoo.erlはyaws.confで指定されているebin_dir配下に保管しておく。 foo.erlは以下。

とりあえずなんで、すごく適当ですが/foo/bar/..というリクエストだった場合には、 valueがbar1,bar2,…となっているjsonデータを返す。 /foo/hogeというリクエストだった場合には、valueがhoge1,hoge2,…となっている jsonデータを返してみる。 (あ、エラー処理とか一切入っていませんので。)

結果は以下。

% curl http://localhost:10080/foo/bar/baz/ 
{"key1":bar1,"key2":"bar2","key3":"bar3"}

% curl http://localhost:10080/foo/hoge/baz/
{"key1":hoge1,"key2":"hoge2","key3":"hoge3"}

% curl http://localhost:10080/foo/hoge/    
{"key1":hoge1,"key2":"hoge2","key3":"hoge3"}

% curl http://localhost:10080/foo/bar  
{"key1":bar1,"key2":"bar2","key3":"bar3"}

なんとか意図通りに動いているみたい。次回はもう少し複雑な処理を入れて遊んでみたいと思います。

Tags: Erlang, Yaws, REST,

Erlangで単純なHTTPサーバ

自作のプログラムにWEB UIをつけようと、色々検討中。 Yawsを使ってもいいんだけど、リッチすぎる気もするし、あまり外部のプログラムと依存したくない。

一旦の結論としては、APIを提供して、WEB UIは別途作成できるようにしようかと。 そしたら無理にErlangじゃなくても、PHPでも何でもいいわけだし。

ってことでAPIを提供するにあたり、色々勉強してみる。 まずはRESTでの提供を考えてみる。

Erlangにはもともとhttpdのモジュールが提供されているため、それを使ってみる。 ここを参考にしてテストしてみる。

バージョンの違いからか、httpdの起動に多少の違いはあるみたい。 自分の環境ではR15Bですが、その場合にはこんな感じ。

Erlang R15B (erts-5.9) [source] [64-bit] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.9  (abort with ^G)
1> c(mod_test).
{ok,mod_test}
2>  inets:start().
ok
3> inets:start(httpd, [{file, "conf/httpd.conf"}]).
{ok,}

あと、mod_test.erlはこんな感じ。

で実際にhttp://localhost:8888/hogeにアクセスしてみると以下のような結果に。

4> 
mod_test :localhost:8888/hoge
mod_test :/hoge
mod_test :localhost:8888/favicon.ico
mod_test :/favicon.ico

なるほど。これは簡単でいいなー。 “absolute_uri”で全部取れるし、”request_uri”でパスの部分が取れるのか。 これを元にrequest_uriをパースして、それ毎に処理を書けば簡単なAPIができそう。

今日はここまで。 あと、JSON-RPCだとこんなのがあるみたい。 これも使ってみたいので、追い追い試してみます。

YawsでTwitter Bootstrapを使ってみる

そろそろこのプログラムに対して 結果確認などができるGUIを本気で作ろうと思ってきた。

どうせならWEB UIの部分もErlangで作ってしまおうってことで、選んだのがYaws。 理由としてはドキュメントが割とそろっているところと、1ファイルにHTMLとプログラムを書き込めるので、お手軽なところ。

さらに、最近の話題に便乗してTwitter Bootstrapも使ってみる。

まずはYawsのインストール。 今回はまだサンプル作成段階なので、さくっとmac portsからインストールする。

% sudo port install yaws

次はYawsの設定。(この段階では結局変更なし) いちおう設定が反映されることを確認するために、起動ポートだけ変更してみる

% sudo cp -p /opt/local/etc/yaws/yaws.conf.template yaws.conf

% sudo vi /opt/local/etc/yaws/yaws.conf

とりあえず80番から10080番に変更してみる

% sudo /opt/local/etc/yaws/yaws.conf.template /opt/local/etc/yaws/yaws.conf
115c115
         port = 10080

で、http://127.0.0.1:10080 にアクセスしてみて、Yawsのページが表示されればOK。

次にTwitter Bootstrapを使う準備。 まずはTwitter BootstrapのページからBootstrapをダウンロード。 配置場所として、今回はyawsのドキュメントルートにassetsというディレクトリを作成し、その配下にしておきます。

% wget http://twitter.github.com/bootstrap/assets/bootstrap.zip
% unzip bootstrap.zip
% cd /opt/local/var/yaws/www
% sudo mkdir assets
% sudo mv bootstrap/* assets/.

Twitter BootstrapはjQueryに依存しているようなので、jQueryもダウンロード。

% wget http://code.jquery.com/jquery-1.7.2.js
% sudo mv jquery-1.7.2.js /opt/local/var/yaws/www/assets/js/.
% ln -s /opt/local/var/yaws/www/assets/js/jquery-1.7.2.js /opt/local/var/yaws/www/assets/js/jquery.js 

これで準備ができた。 Yawsについてはここを参考に、 Twitter bootstrapについては、ここを参考にした。

まずは単純にHTMLで作ってテストしてみる。

適当な名前でyawsのドキュメントルート配下に保存。 test.htmlだとすると、http://127.0.0.1:10080/test.htmlでアクセスしてみる。 お、ちゃんとTwitter Bootstrapも適用されてるみたい。 (上のコードだとあんまりわからないかもですが、他にも色々試してます)

でこれをもとにコードを書いてみる。 ゆくゆくは動的ページを生成したいため、Yawsのマニュアルを参考に、動的ページを出力されせる方法を採用。(マニュアルの12ページあたりを参照) しかしどの言語でもhtmlを生成する部分ってのは面倒くさいな。

さっき書いたHTMLを元にehtmlに変換していく。で、変換後のコードが以下。

これをtest.yawsとしてYawsのドキュメントルート配下に保管。 で、アクセスしてみる。http://127.0.0.1:10080/test.yaws うん。test.htmlを同じ表示になってる。

今回ブログには載せきれていないけど、Twitter bootstrapも色々試してみたりして、 なんとなく理解できてきた。とっつきやすくていいかもしれない。

とりあえず目的であるWEB UI作成のときにはTwitter Bootstrapをベースで進めていくことにします。また形になったところでブログに載せようと思います。

MochiWebコードリーディング #1-1

まずは mochiweb_socket_server.erl から読み始めていきます。 ていっても2月に読んだメモからブログの記事におこしています。

最初はstart/1関数から読みといていきます。 start/1関数の全体はこう

start(Options) ->
    case lists:keytake(link, 1, Options) of
        {value, {_Key, false}, Options1} ->
            start_server(start, parse_options(Options1));
        _ ->
            %% TODO: https://github.com/mochi/mochiweb/issues/58
            %% [X] Phase 1: Add new APIs (Sep 2011)
            %% [_] Phase 2: Add deprecation warning
            %% [_] Phase 3: Change default to {link, false} and ignore link
            %% [_] Phase 4: Add deprecation warning for {link, _} option
            %% [_] Phase 5: Remove support for {link, _} option
            start_link(Options)
    end.

では

   start(Options) ->
     case lists:keytake(link, 1, Options) of

ここでkeytake関数がでてきた。keytake関数の定義は以下

       keytake(Key, N, TupleList1) -> {value, Tuple, TupleList2} | false

TupleList1からlinkアトムがキーとなっている1番目のタプルを取得 戻り値はヒットしたタプルと元のタプルリスト

mochiwebに戻ると、つまりOptionsリストからlinkがキーとなっている一番目の要素を取得 取得した結果をcaseで評価される式としている

   {value, {_Key, false}, Options1} ->

戻り値がこのパターンにマッチした場合、

   start_server(start, parse_options(Options1));

start_server関数が呼ばれる。 引数は先頭にstartのアトムとOptions1をparse_options/1関数に渡した結果

     _ ->
       start_link(Options)

上記のパターンにマッチしない場合、 start_link関数が呼ばれる。引数はOptions変数

次に、case式の評価結果によって呼ばれるparse_options関数へ進みます。

   parse_options(Options) ->
     parse_options(Options, #mochiweb_socket_server{}).

parse_options/2関数を呼び出し 第一引数はOptions、第二引数はmochiweb_socket_serverレコード

   parse_options([], State) ->
     State;

一つ目の引数が空、つまりOptions変数が空の場合

   parse_options([{name, L} | Rest], State) when is_list(L) ->
     Name = {local, list_to_atom(L)},
     parse_options(Rest, State#mochiweb_socket_server{name=Name});

先頭がリストかつその一つ目の要素がタプルで、先頭がnameのアトムをキーとする要素だった場合、 さらにnameをキーとする要素の値Lがリストの場合 nameの値がリストってどういう場合??? -> 文字列として渡した場合か。その場合はアトムに直すと。 {local, list_to_atom(L)}のタプルをNameに束縛 さらにparseoption/2関数を呼び出す 引数はOptionのcdr部とStateに束縛されているmochiweb_socket_serverレコードのnameにNameを束縛したもの

   parse_options([{name, A} | Rest], State) when A =:= undefined ->
     parse_options(Rest, State#mochiweb_socket_server{name=A});

先頭がリストかつその一つ目の要素がタプルで、先頭がnameのアトムをキーとする要素だった場合、 さらにnameをキーとする要素の値Aがundefinedだった場合。

Tags: Erlang, MochiWeb,

BunnyでRabbitMQ

ちょっとだけRabbitMQで遊んでみたので、そのメモ。

今回はクライアントとしてrubyを選択。 rubyのamqpライブラリも複数ある様子。いくつか触ってみて、一番簡単?わかりやすかったbunnyというライブラリで遊んでみる。

Web上にいくつかサンプルがあったので、それをもとにいろいろ試してみる

まずはrubyの準備から。

% sudo gem install bunny

クライアント(publisher)のコード

受け側(subscriber)のコード

で、簡単にテスト。 どうやらloggingのオプションがあるみたいなので、それも試してみる。(上のコードには入ってません)

 
b = Bunny.new(:host => '192.168.0.1', :logging =>true)

でまずはQueueに溜めてみることから。

% ruby publisher.rb
I, [2012-03-20 22:00:43#22098]  INFO -- received: #"Licensed under the MPL.  See http://www.rabbitmq.com/", :version=>"2.6.1", :product=>"RabbitMQ", :platform=>"Erlang/OTP", :copyright=>"Copyright (C) 2007-2011 VMware, Inc.", :capabilities=>{}}>, @channel=0>
I, [2012-03-20 22:00:43#22098]  INFO -- send: #"guest", :PASSWORD=>"guest"}, @mechanism="AMQPLAIN", @client_properties={:information=>"http://github.com/ruby-amqp/bunny", :version=>"0.7.9", :product=>"Bunny", :platform=>"Ruby"}>, @channel=0>
I, [2012-03-20 22:00:43#22098]  INFO -- received: #, @channel=0>
I, [2012-03-20 22:00:43#22098]  INFO -- send: #, @channel=0>
I, [2012-03-20 22:00:43#22098]  INFO -- send: #, @channel=0>
I, [2012-03-20 22:00:43#22098]  INFO -- received: #, @channel=0>
I, [2012-03-20 22:00:44#22098]  INFO -- send: #, @channel=1>
I, [2012-03-20 22:00:44#22098]  INFO -- received: #
I, [2012-03-20 22:00:44#22098]  INFO -- send: #, @channel=1>
I, [2012-03-20 22:00:44#22098]  INFO -- received: #, @channel=1>
I, [2012-03-20 22:00:44#22098]  INFO -- send: #, @channel=1>
I, [2012-03-20 22:00:44#22098]  INFO -- send: #"application/octet-stream", :delivery_mode=>1, :priority=>0}, @weight=0, @size=17>, @channel=1>
I, [2012-03-20 22:00:44#22098]  INFO -- send: #

ログの最後を見るとどうやら入ったみたい。 でこれを数回繰替えしていくつかキューに溜めてみる。

次に取り出しをしてみる。

% ruby subscriber.rb Message count: 6 This is the message: hello, everybody!

putsで出力している内容に現在キューに溜まっている数が表示されている。 どうやらbunnyでは

#{q.message_count}

で現在のキューをカウントできるらしい。

もう一回やってみると、カウントも減っている

% ruby subscriber.rb
Message count: 5
This is the message: hello, everybody!

ここまでで一番簡単な動作確認ができた。 他にもいろいろ試していたんですが、動作確認までは取れず。 まだまだRabbitMQを理解するには遠いなー。

とりあえずはexchangeの名前のqueueの名前とrouting_keyの関係について整理するところから始めます。 掲載したコードではexchange名は空で勝手につくられるようになっているけど、 これを指定するとメッセージを取れなくなってしまう(keyと同じ名前でもダメ)ので、ここら辺を理解しないと。

Tags: RabbitMQ, Ruby, Bunny,

久々のRuby on Rails

久しぶりにRuby on Railsを触ってみた。 Rails3を触ってみたんですが、4年ぐらい前にくらべると格段に準備が簡単になった気がする。 自分にフレームワーク系の知識がついたのか、受け入れられうようになっただけなのかもしれないけど。

ここ数年はscheme(Gauche)やErlangばかりだったので、何だか新鮮。 そしてWebでの情報の多さにびっくりする。 やっぱりここまで情報があるからこそ皆がどんどん使っていけるんだろうなー。

あとは、コマンドが充実してるのもあるな。コマンドを打つとそれに従って、 必要なファイルが必要な場所に出来上がったりするのはやっぱり便利だ。

あ、Railsで作ったアプリはHerokuかdotcloudで公開しようと思ってます。 今はどっちのほうが使い勝手がいいか、試行錯誤中。

色んなPaaSを使ったりして、将来的にはErlang PaaSとかsheme PaaSとかを遊びで作ってみたいな。

コードリーディング

RabbitMQのコードはかなり難解で、コードリーディングがなかなか捗りません。 ってことで最近は比較的簡単だと言われる、mochiwebのコードを読みはじめました。

現在、ここに載せるようにネタを書き溜め中。 今週末ぐらいからちょくちょく更新していこうと思います。

RabbitMQを触ってみる(Code Readingの準備)

今回は内容がありません。最初にゴメンナサイしておきます。 (ブログに書くほど捗ってません。。。)

大体AMQPの概要がつかめてきたので、Code Readingに入っていきたいと思います。 Code Readingといっても、最初からコードを追える量でもなさそうなので、Unit Testのコードから読み解いていきたいと思います。

まずは、RabbitMQのソースコードを落してくるところから。

RabbitMQのソースコードはMercurialで管理されています。 自分の環境にはMercurialが入っていなかったので、Mercurialをインストールするところから

% sudo port install Mercurial

Mecurialが入ったら、次はコードを落してきます。 RabbitMQ関連のリポジトリは複数あるようですが、まずは本命のrabbitmq-serverから。

% hg clone http://hg.rabbitmq.com/rabbitmq-server

自分はemacsを使っているので、次にコードリーディングの際はetagsで関数を検索したりするため、そのためのtagを作ります。 まだ構造はわかっていませんが、とりあえずソースコードが置いてありそうなディレクトリを対象とします。

% cd rabbitmq-server
% etags src/*.erl include/*.hrl

これでひとまず準備は完了です。足りなくなったら都度整えていきたいと思います。

% cd src/
% ls -l |grep "test"

で、今ソースコードを読み始めているわけですが、ここに書けるほどではないので、また次回以降にまとめたいと思います。

RabbitMQを触ってみる(AMQP勉強編)

先週より引き続きですが、RabbitMQの勉強がしたい!って思いつきで始めました。

しかし日本語のドキュメントも少く、すぐには使い始められそうにありません。 ということで、まずはCode Readingから始めていこうかなーと思ってます。(RabbitMQを使い始めるのはまだだ道のりは長そうだ。。。)

Code Readingをするにあたり、まずはRabbitMQがどういう動きをするのかの事前知識が必要だと思うので、今週はRabbitMQというよりは メッセージング・ミドルウェアの標準であるAMQPの概念から勉強していきます。(先週も書いた通り、RabbitMQはAMQPの実装です)

とりあえずざっくりと概要をまとめてみます。

AMQPでは、クライアント・サーバ(brokerって言うのかな)間のプロトコルとネットワークプロトコルが仕様として定義されている。 またbroker構成要素として Exchange, Binding, Message Queueの3つがある。クライアントは転送元のPublishersと転送先のConsumersがある。

Exchangeとは、Publishers(クライアントアプリケーション)からのmessageを受けとり、Bindingに従ってMessage Queueに転送する。 BindingとはExchangeとMessage Queueの関係を定義している。(ExchangeからMessage Queueへのルーティングを定義) Message QueueとはPublisherから転送されてきたMessageを蓄積し、後続のConsumer Applicationに引き渡す。 (1.0からはExchangeとMessage Queueは一つになるらしい。これは後で仕様を読みます)

Bindingのルーティングパターンは複数ある。

Direct
1. A message queue binds to the exchange using a routing key, K.
2. A publisher sends the exchange a message with the routing key R.
3. The message is passed to the message queue if K = R.

これは、Bindingで定義したkeyと一致したものを該当のMessage Queueに転送するってことかな。 keyは完全一致だと思われる。

Fanout
1. A message queue binds to the exchange with no arguments.
2. A publisher sends the exchange a message.
3. The message is passed to the message queue unconditionally.

これは引数(Routing Key?)がない場合には、無条件にキューに転送するってことか。複数のキューがあった場合には全てのキューに転送される。

Topic Headers
1. A message queue binds to the exchange using a routing pattern, P.
2. A publisher sends the exchange a message with the routing key R.
3. The message is passed to the message queue if R matches P.

これはパターンマッチで条件指定ってことで、条件にマッチしたBindingが定義されているMessage QueueにMessageを転送する。

The Headers Exchange Type
1. A message queue is bound to the exchange with a table of arguments containing the headers to be matched for that binding and optionally the values they should hold. The routing key is not used.
2. A publisher sends a message to the exchange where the ‘headers’ property contains a table of names and values.
3. The message is passed to the queue if the headers property matches the arguments with which the queue was bound.

これはRouting Keyを使わずにMessageフォーマットのheaderの中のx-matchで条件を判断するのかな。

間違っている箇所などあればご指摘頂けると嬉しいです。

とりあえず、すごくざっくりの概要としてはこんなもんだろうか。 引き続き、Code Readingに向けて仕様を読んでいきます。