ごろねこ日記

2012-04-29

Erlang R15B01 をDebianにインストールした時のメモ

入れたてのDebianErlangインストールしました。
手順は以下。

依存関係パッケージ入れる(全部は必要ないかも)

$ sudo aptitude install make gcc g++ tcl8.5
$ sudo aptitude install openjdk-6-jdk openjdk-6-jre openjdk-6-jre-lib openjdk-6-source
$ sudo aptitude install unixodbc unixodbc-dev unixodbc-bin

最新版のソースコードを落とす

$ wget http://www.erlang.org/download/otp_src_R15B01.tar.gz

解凍

$ tar xvzf otp_src_R15B01.tar.gz

ディレクトリに入ってconfigure, make, make install

$ cd otp_src_R15B01
$ ./configure --enable-smp-support --enable-kernel-poll
$ make
$ sudo make install

動作確認

$ erl
Erlang R15B01 (erts-5.9.1) [source] [async-threads:0] [kernel-poll:false]

Eshell V5.9.1  (abort with ^G)
1> 

    • enable-smp-supportにしてみたものの、よく考えたら仮想環境でCPU一個しか無かった Orz

2012-01-21

Beagleboard-xmでAndroid動かしたときのメモ

購入品
記念撮影
f:id:hiroe_orz17:20120108122527j:image:w360

詳細な購入品リスト(背景が灰色のものは検討したものの購入していないものです)。
最終的な金額は液晶も含めて4万2千円程度でした。

参考にさせて頂いた書籍
そもそも組み込みのAndroidに興味を持ったのは「組み込みAndroid」という書籍を読んだからです。beagleboard-xmへのAndroidポーティングに関しては若干内容が古いですが、大変参考になります。

基礎から学ぶ 組み込みAndroid

基礎から学ぶ 組み込みAndroid



参考にさせて頂いたサイト
TI-Android-GingerBread-2.3.4-DevKit-2.1 DeveloperGuide
BeagleBoard-xM に Android Gingerbread をポーティング

なお、上の写真に移っているタッチパネルディスプレイ(HM-TL7T)でタッチパネル機能を使えるようにするためにはLinuxカーネルソースに含まれるファイルを若干修正する必要がありました。
これには「LinuxでHM-TL10Tのタッチパネルを認識させる」を参考にさせていただきました。
デバイスドライバのことなどまったく分からないのでこのようなサイトがあって大変助かりました。

事前の準備
GingerBreadをコンパイルするには64bitのUbuntu Linuxが必要です。私は手元のMacBookProにUbuntu用のパーティションを作ってダブルブート環境にしました。
Macで使えるUbuntu Linux 64bit版はこちらにあります。
Ubuntu 11.10 (Oneiric Ocelot)

既にMacインストールしている場合もディスクユーティリティで新たにUbuntu用のパーティションを作成可能です。ただしその場合、BootCampWindowsデュアルブートする環境にしていたらWindowsが起動できなくなる可能性が高いですので必ずバックアップは取っておいてください。私は起動しなくなりましたw

MacBookProにUbuntuインストールする手順については以下のサイトを参考にさせていただきました。
defiantの日記

Ubuntuインストールできたら端末から必要なパッケージをインストールします。

$ sudo aptitude install git-core gnupg flex bison gperf build-essential zip curl sun-java6-jdk zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev ia32-libs x11proto-core-dev libx11-dev lib32readline5-dev lib32z-dev uboot-mkimage

gcc関連をインストール

$ sudo apt-get install gcc-4.5
$ sudo apt-get install gcc-4.5-multilib
$ sudo apt-get install g++-4.5
$ sudo apt-get install g++-4.5-multilib
$ export CC=gcc-4.5
$ export CXX=g++-4.5

ソースコードその他をダウンロードする
必要なソースコードその他はTexus Instruments のダウンロードサイトにおいてあります。

Texus InstrumentsのサイトにはGingerBreadに関して2.3と2.3.4の2種類がありますが、最新の [beagleboard-xm RevC] 上でGingerBreadを動かす場合は必ず2.3.4の方を使用してください。2.3ではマウスネットワークデバイスの認識に関してRevCには対応していないらしく、そのままではマウスネットワークも認識しません。

なお、ここでのこれ以降の作業は"~/src/beagle-android/GingerBread_2_3_4"というディレクトリを作って、そこにソースと開発キットをダウンロードして行うことを前提としています。

$ mkdir -p ~/src/beagle-android/GingerBread_2_3_4
$ cd ~/src/beagle-android/GingerBread_2_3_4
$ wget http://software-dl.ti.com/dsps/dsps_public_sw/sdo_tii/TI_Android_DevKit/TI_Android_GingerBread_2_3_4_DevKit_2_1/exports/TI_Android_GingerBread_2_3_4Sources.tar.gz

同じくTexus Instrumentのサイトから開発キットをダウンロードする。

$ wget http://software-dl.ti.com/dsps/dsps_public_sw/sdo_tii/TI_Android_DevKit/TI_Android_GingerBread_2_3_4_DevKit_2_1/exports/TI_Android_GingerBread_2_3_4_DevKit_2_1.tar.gz

あとはbeagleboard-xm向けのビルド済みパッケージも落としておくといいと思います。私の環境では自前でコンパイルしたMLOとu-boot.binが動作しなかったので、ビルド済みパッケージに含まれていたものを使わせてもらいました。
ビルドできました

$ wget http://software-dl.ti.com/dsps/dsps_public_sw/sdo_tii/TI_Android_DevKit/TI_Android_GingerBread_2_3_4_DevKit_2_1/exports/beagleboard-xm.tar.gz

ダウンロードには数時間かかります。寝る前に上記2つを別の端末で実行しておいて寝て待つのがいいと思われます。

ビルドの準備
ダウンロードが終わったらTI-Android-GingerBread-2.3-4DevKit-2.1 DeveloperGuideに従ってAndroidビルドします。

まずは解凍

$ tar xvzf TI_Android_GingerBread_2_3_4Sources.tar.gz
$ tar xvzf TI_Android_GingerBread_2_3_4_DevKit_2_1.tar.gz 

ソースディレクトリに移動してレポジトリをセットアップします。repoについては正直よく分かってないところがあるのですが、内部でgitを操作しているらしく、下記を実行するとディレクトリソースコードが配置されます。

$ cd TI_Android_GingerBread_2_3_4Sources/
$ ./.repo/repo/repo sync --local-only

ツールのあるディレクトリにパスを通しておきます。

export PATH=~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4Sources/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin:$PATH

Beagleboard-XMにおけるAndroidの起動プロセス
予備知識として、beagleboard-xm上でのブートプロセスは以下のようになるようです。
(起動時の出力を見ての憶測に基づいたものであり、ソースを見たわけではありません)

  1. beagleboard-xm電源投入
  2. (beagleboard-xm内のブートローダ起動?)
  3. microSDカードのbootパーティションの最初にあるブートローダ"MLO"を起動
  4. microSDカードのbootパーティションにある"u-boot.bin"を起動
  5. microSDカードのbootパーティションにある"boot.scr"を実行
  6. microSDカードのbootパーティションにある"uImage"を起動
  7. Linuxカーネルが展開されて起動プロセスに入る
  8. Android起動

ここからの過程では上記のMLO, u-boot.bin, boot.scr, uImage、そしてAndroidビルドしていきます。

ブートローダ(MLO)のビルド
MLOのビルドはソースディレクトリ内のx-loaderディレクトリに移動して行います。

$ cd x-loader
$ make CROSS_COMPILE=arm-eabi- distclean
$ make CROSS_COMPILE=arm-eabi- omapbeagle_config
$ make CROSS_COMPILE=arm-eabi-

できあがったx-load.binからデベロッパーキットに含まれるsignGPを使ってMLOを作ります。

$ ../../TI_Android_GingerBread_2_3_4_DevKit_1_0/Tools/signGP/signGP ./x-load.bin

x-load.bin.iftというファイルが作られるので、これをMLOにリネームして、実行権を付加しておきます。

$ mv x-load.bin.ift MLO
$ chmod 755 MLO

u-boot.binのビルド
u-boot.binはソースディレクトリ以下のu-bootディレクトリで行います。

$ cd u-boot
$ make CROSS_COMPILE=arm-eabi- distclean
$ make CROSS_COMPILE=arm-eabi- omap3_beagle_config
$ make CROSS_COMPILE=arm-eabi-

これでu-boot.binが作られます。
ここでカレントディレクトリにあるtoolsディレクトリ内にmkimageというファイルが作られています。この後の工程で使用しますので"/usr/bin"または"/usr/local/bin"、"~/bin"など、パスの通っているディレクトリにコピーしておいてください。
私の環境では~/binにパスが通ってますのでそこにコピーしておきました。

$ cp tools/mkimage ~/bin/

boot.scrの作成
boot.scrの作成はデベロッパーキットに移動して行います。

$ cd ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4_DevKit_2_1/Tools/mk-bootscr/
$ ./mkbootscr

これで一旦boot.scrが作られますが、内容をカスタマイズしたいのでboot.scriptというファイルを以下の内容で、同じディレクトリ内に作ります。

mmc init
fatload mmc 1 80200000 uImage
setenv bootargs 'console=ttyO2,115200n8 androidboot.console=ttyO2 root=/dev/mmcblk0p2 rw rootfstype=ext3 rootdelay=1 init=/init ip=dhcp omap_vout.vid1_static_vrfb_alloc=y vram=12M omapfb.vram=0:8M,1:4M,2:4M omapfb.mode=dvi:800x600@60 omapdss.def_disp=dvi'
bootm 0x80200000

そしてこれを元に以下のコマンドでboot.scrを作成します。

$ sudo ./mkimage -A arm -O linux -T script -C none -a 0 -e 0 -n "BeagleBoard" -d boot.script boot.scr

Linuxカーネルビルド
Linuxカーネルコンパイルを行います。Linuxカーネルは上で落としたAndroidソースコードkernelディレクトリ内にあります。

$ cd kernel
$ export CC=gcc-4.5
$ export CXX=g++-4.5
$ make ARCH=arm CROSS_COMPILE=arm-eabi- distclean
$ make ARCH=arm CROSS_COMPILE=arm-eabi- omap3_beagle_android_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-eabi- uImage

Androidビルド
つづいてAndroidコンパイルします。
私の環境ではmakeを実行した際にエラーが発生しましたので実際のコンパイルに入る前にMakefileを修正しておきます。

$ vim build/core/combo/HOST_linux-x86.mk

このファイル中の"HOST_GLOBAL_CFLAGS..." から始まる行をコメントアウトします。

HOST_GLOBAL_CFLAGS += -D_FORTIFY_SOURCE=0
  ↑
# HOST_GLOBAL_CFLAGS += -D_FORTIFY_SOURCE=0

それからAndroidコンパイルにはJava6が必要ですが、64bit版Ubuntuaptで入る64bit版Javaではコンパイル途中でエラーが発生します。

そこで、Sunのホームページから32bit版のJava6を落として、update-alternativesコマンドで32bit版Javaを使うように設定した上でコンパイルを開始します。
Sunのホームページから32bit版Javajdk-6u30-linux-i586.bin)をダウンロードします。
/usr/localにコピーして解凍し、システムがそちらのJavaを使うように設定します。

$ sudo cp jdk-6u30-linux-i586.bin /usr/local/
$ cd /usr/local
$ sudo ./jdk-6u30-linux-i586.bin
$ sudo update-alternatives --install "/usr/bin/java" "java" "/usr/local/jdk1.6.0_30/bin/java" 1
$ sudo update-alternatives --config java

ここで表示された設定画面で/usr/local/jdk1.6.0_30/bin/java の番号を選択する。

alternative java (/usr/bin/java を提供) には 4 個の選択肢があります。

  選択肢    パス                                    優先度  状態
------------------------------------------------------------
 * 0            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      自動モード
  1            /usr/lib/jvm/java-1.5.0-sun/jre/bin/java   53        手動モード
  2            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      手動モード
  3            /usr/lib/jvm/java-6-sun/jre/bin/java       63        手動モード
  4            /usr/local/jdk1.6.0_30/bin/java            1         手動モード

現在の選択 [*] を保持するには Enter、さもなければ選択肢の番号のキーを押してください:4

設定したら再度上記コマンドを実行して32bit版Javaが選択されていることを確認してください。

ここまでできたら、いよいよAndroidコンパイルします。
ここで-jオプションで並列実行数を指定します。-j5の場合は5つのビルドプロセスが同時に実行されます。
「組み込みAndoid」によると通常は(コア数+1)を指定するとのことなのでMacBookPro 2コア(ハイパースレッディングで4コア)では-j5を指定します。

$ export CC=gcc-4.5
$ export CXX=g++-4.5
$ make TARGET_PRODUCT=beagleboard OMAPES=5.x -j5

MacBookPro 2コア(ハイパースレッディングで4コア)では35分程度で完了しました。

microSDカードへのポーティング
microSDカードへインストールします。
まずはホームディレクトリ以下に”images”ディレクトリを作って必要なものを全てそこに集めます。

$ mkdir ~/images
$ cd ~/images

uImageを持ってきます。

$ cp ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4Sources/kernel/arch/arm/boot/uImage ~/images/

Androidディレクトリツリーを作って~/images以下にコピーします。

$ cd ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4Sources/out/target/product/beagleboard/
$ mkdir android_rootfs
$ cp -r root/* android_rootfs/
$ cp -r system android_rootfs/
$ sudo ../../../../build/tools/mktarball.sh ../../../host/linux-x86/bin/fs_get_stats android_rootfs . rootfs rootfs.tar.bz2
$ cp rootfs.tar.bz2 ~/images/

MLOとu-boot.bin、そしてboot.scrをコピーします。

$ cd ~/images
$ cp ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4Sources/x-loader/MLO ./
$ cp ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4Sources/u-boot/u-boot.bin ./
$ cp ~/src/beagle-android/GingerBread_2_3_4/TI_Android_GingerBread_2_3_4_DevKit_2_1/Tools/mk-bootscr/boot.scr ./

また、プレビルドパッケージからmkmmc-android.shとMedia_Clipsを持ってきます。

$ cp ~/src/android-prebuild/android-gingerbread/beagleboard-xm/mkmmc-android.sh ./
$ cp -r  ../TI_Android_GingerBread_2_3_DevKit_1_0/Prebuilt_Images/beagleboard-xm/Media_Clips ~/images/

この時点で~/imagesディレクトリ以下には以下のファイルがあることを確認してください。

  • MLO
  • boot.scr
  • rootfs.tar.bz2
  • uImage
  • Media_Clips
  • mkmmc-android.sh
  • u-boot.bin

microSDカードにインストールします。ここで使うmkmmc-android.shは日本語環境では正常に動作しないのでLANG=Cをセットしておきます。
また、コマンド中の/dev/sdcは環境によって異なります。syslogdfコマンドでデバイスを確認してください。

$ export LANG=C
$ sudo ./mkmmc-android.sh /dev/sdc MLO u-boot.bin uImage boot.scr rootfs.tar.bz2 Media_Clips

ここではmkmmc-android.shを使ってmicroSDカードにファイルを配置しますが、中でやっている事は

  1. microSDカードにboot, rootfs, dataの3つのパーティションを切る。
  2. パーティションをフォーマット
  3. MLO, u-boot.bin boot.scr uImageをbootパーティションに配置
  4. rootfs.tar.bz2をrootfsパーティションに展開
  5. MediaClipsをdataパーティションにコピー

となります。これを手動で行う事ももちろん可能ですが、その場合はbootパーティションフォーマット後のファイルのコピー順が重要となるそうです。最初は必ずMLO、続いてu-boot.binをコピーしないと正常に起動しません。それはbeagleboard-xmがbootパーティションの最初のセクタからMLOを探すかららしいです。
また、後からファイルを更新する時は必ず上書きコピーするように「組み込みAndroid」に書いてありました。一旦削除してから保存し直すとうまく起動出来ないようです。


Android起動

あとはこれをbeagleboard-xmに差して電源を入れますが、起動ログを確認するためにRS232C-USB変換ケーブルでbeagleboard-xmとパソコンをつなぎます。
そしてscreenコマンドを入れて起動ログを見られるようにします。

$ sudo apt-get install screen
$ screen /dev/ttyUSB0 115200

上記を実行すると端末上にbeagleboardのログが流れる準備が整いました(beagleboardを起動するまでは何も表示されません)

あとはbeagleboard-xmを起動します。初回起動時はかなり待たされます(10〜15分)ので、ドキドキしながら気長に待ちましょうw

キタ━━━ヽ(∀゚ )人(゚∀゚)人( ゚∀)ノ━━━!!
f:id:hiroe_orz17:20120121120256j:image:w360

「LinuxでHM-TL10Tのタッチパネルを認識させる」に従ってデバイスドライバの修正とカーネルビルドの設定を行ったところ、無事タッチパネルも使用できました。
f:id:hiroe_orz17:20120123012719j:image:w360
↑嬉々としてタッチするの図。ただしこのタッチパネルスクリーンは爪の先でタッチしないと反応が悪いですw
起動後暫くは内部でロードを行っているらしく動きがトロいのですが、それが落ちつくとわりとサクサクと操作できます。

2011-12-08

Emacsで文字列中の改行を全部消す

ログにプログラム間でのやり取りの際の電文を書き出させてるんだけど、これを端末から直接コピーすると改行が入っちゃう。
けど電文は結構長いのもあるので一個一個消すなど大変なので一発で消す方法のメモ。

M-x replace-string 置換前 C-q C-j 置換後 (何も入力しない)
Enter

置換前

000001111222233
122223333
111111111111111111111111111111111
333333333333333333333333333333333

置換後

000001111222233122223333111111111111111111111111111111111333333333333333333333333333333333

2011-12-01

Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その5)

さて、なんかMochiWebをさらっと触ってみたシリーズも第5弾となりました。ちょっとこのタイトルではじめた事を後悔しつつありますが、気を取り直していってみましょ。

過去記事はこちら
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その2)
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その3)
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その4)

今回はSNSなどでは必須となるログイン機構と、それに伴うセッションの実装です。
僕自身、ログインに関しては今までほとんどをフレームワーク任せだったのでちと緊張しますが頑張ってみます←
ご指摘等は歓迎しますので泣かない程度にやってやってください。

セッションの基本的な動作
セッションサーバ側に各クライアント毎のデータを保存しておいてページ感で情報を共有する為の仕組みです。
そして通常は、どのデータがどのクライアントのものかを見分ける為にクッキーを使います。クッキーに保存したキーを元にして、そのクライアントのデータを探し出すわけです。
従って一般的な流れとしては

  1. ログイン
  2. セッションログイン情報を保存
  3. クライアントのクッキーにに、セッションIDを保存
  4. 次回からのアクセスでは都度、クライアントからセッションIDが送られて来るので、それをもとにセッションデータを参照、更新する

という流れとなります。

実装
上記を実装したコードが以下になります。
今回は動作原理を理解する為にmemcachedのようなキーバリューストアは使いません。てか、データベースすら使わずハードコーディングしています(笑) まあ何事も一歩一歩という事でw

-module(session_action).

-include_lib("eunit/include/eunit.hrl").

-export([start/1, stop/0]).


start(Port) ->
    mochiweb_http:start([{port, Port}, {loop, fun dispatch/1}]).

stop()->
    mochiweb_http:stop().

%%%===================================================================
%%% Reqest Dispatcher
%%%===================================================================

dispatch(Req) ->
    Path = Req:get(path),
    Params = Req:parse_qs(),

    case string:tokens(Path, "/") of
        [Controller, Action | UrlParams] ->
            handle(Controller, Action, UrlParams, Params, Req);
        [Controller] ->
            handle(Controller, "index", [], Params, Req);
        [] ->
            handle("top", "index", [], [], Req)
    end.

%%%===================================================================
%%% Reqest Handlers
%%%===================================================================

%% Topページ
handle("top", "index", _Path, _Params, Req) ->
    Req:ok({"text/html", "Welcome!"});

%% ユーザページ
handle("user", "welcome", [Name], _Params, Req) ->
    Req:ok({"text/html", subst("Welcom(in url) ~s!", [Name])});

%% ユーザ名とパスワードで認証する。成功したらセッションIDをクッキーに保存する。
handle("auth", "login", [], Params, Req) ->
    Username = proplists:get_value("username", Params),
    Password = proplists:get_value("password", Params),

    case authenticate(Username, Password) of
        {ok, UserId} ->
            SessionId  = get_session_keys(Username, Password, UserId),
            create_new_session(SessionId, UserId),
            Header1 = set_cookie("session_id", SessionId),
            Header2 = set_cookie("user_id", UserId),
            Req:ok({"text/html", [Header1, Header2], <<"ok! logged in :-)">>});
        {error, login_failure} ->
            Req:respond({403, [{"Content-Type", "text/html"}], 
                         <<"UnAuthenticated :-<">>})
    end;

%% ログアウト処理
handle("auth", "logout", [], _Params, Req) ->
    Header1 = set_cookie("session_id", ""),
    Header2 = set_cookie("user_id", ""),
    Req:ok({"text/html", [Header1, Header2], <<"logged out.">>});

%% ログイン後でないと見る事の出来ないコンテンツ
handle("secure_space", "index", [], _Params, Req) ->
    case check_loggedin(Req) of
        ok ->
            Name = get_session_value("name", Req),
            Req:ok({"text/html", subst("There is Secure Space (~s)", [Name])});
        {error, unauthenticated} ->
            Req:respond({403, [{"Content-Type", "text/html"}], 
                         <<"UnAuthenticated :-<">>})
    end;

%% 404 Not Found
handle(_, _, _, _, Req) ->
    Req:not_found().

%%%===================================================================
%%% Internal Functions
%%%===================================================================

set_cookie(Key, Value) ->
    mochiweb_cookies:cookie(Key, Value, [{path, "/"}]).


%% 既にログイン済みかどうか確認する。
check_loggedin(Req) ->
    case get_session_value("loggedin", Req) of
        true -> ok;
        _ -> {error, unauthenticated}
    end.

%% ユーザ認証を行う。
authenticate(Username, Password) ->
    case [Username, Password] of
        ["shin", "mypassword1"] -> {ok, "1"};
        ["tuna", "mypassword2"] -> {ok, "2"};
        _                       -> {error, login_failure}
    end.

%% ユーザ情報からセッションIDを生成する。
get_session_keys(Username, _Password, _UserId) ->
    case Username of
        "shin" -> "aaabbbcccddd";
        "tuna" -> "eeefffggghhh"
    end.     

%% セッションデータから与えられたキーに対応する値を返す。
get_session_value(Key, Req) ->
    case Req:get_cookie_value("session_id") of
        undefined ->  undefined;
        SessionId ->
            SessionData = get_session_data(SessionId),
            UserId = Req:get_cookie_value("user_id"),
            case proplists:get_value("id", SessionData) of
                UserId -> proplists:get_value(Key, SessionData);
                _ -> undefined
            end
    end.

%% ログイン時にセッションにユーザデータ等を保存する関数。今回はなにもしない。
create_new_session(_SessionId, _UserId) ->
    ok.

%% 与えられたセッションキーに対応するセッション情報を返す。
get_session_data(SessionId) ->
    case SessionId of
        "aaabbbcccddd" ->
            [{"id", "1"},
             {"name", "shin"}, 
             {"age", 36}, 
             {"sex", "male"},
             {"lang", ["ruby", "objective-c", "erlang"]},
             {"loggedin", true}];
        "eeefffggghhh" ->
            [{"id", "2"},
             {"name", "tuna"}, 
             {"age", 3}, 
             {"sex", "male"},
             {"lang", ["cat-lang"]},
             {"loggedin", true}];
        _ ->
            []
    end.

subst(Template, Values) ->
    list_to_binary(lists:flatten(io_lib:fwrite(Template, Values))).

いろいろとつっこみ所があるのは間違いないです(笑)
まあ、何はともあれ遊んでみましょうw

動作検証
今回はchromeを使いました。chromeではDeveloper Toolsにクッキーの値が見れるツールがあるので便利です。

まずはいつも通りErlangノードを起動してコンパイルします

$ erl -pa ../mochiweb

c(session_action).

9999番ポートでWebサーバを起動します。

session_action:start(9999)

ブラウザhttp://localhost:9999アクセスすると。
f:id:hiroe_orz17:20111202005742p:image
ちなみにこの時点でのクッキーは
f:id:hiroe_orz17:20111202005841p:image
なんもありません。

では、この状態で認証が必要な http://localhost:9999/secure_spaceアクセスしてみます。
f:id:hiroe_orz17:20111202010028p:image
見事に蹴られましたw

では
http://localhost:9999/auth/login?username=shin&password=mypassword1
アクセスしてログインしてみます。ちなみに、ここでは思いっきりgetパラメータとしてユーザIDとパスワードを渡してますが、これは本番ではやっちゃいけないです。必ずpostとして渡すべきです。
ここではpostログインの為のページ作るのめんどくさいのでgetで渡していますw
結果は
f:id:hiroe_orz17:20111202010345p:image
おkみたいです。あっさりしてて実感ができませんがログインできました。

再び、さきほど表示出来なかったページを開いてみます。
http://localhost:9999/secure_space
f:id:hiroe_orz17:20111202010526p:image
おお〜。表示されました。

ちなみにこの時のクッキーの状態は
f:id:hiroe_orz17:20111202010839p:image
お、ちゃんとセッションIDとユーザIDが保存されています。

所感
今回はまずはコード内で全てすましてセッションとそれを使ったログイン機構を実装してみました。
殆どの場合、こういったものはフレームワークプラグインとして実装されているのであまり自分で実装する機会は少ないですが実際やってみると割と簡単ですね。
次回は何らかのキーバリューストアを使って実際にセッションデータを保存してみたいと思います。ErlangについてくるMnesiaを使うか、少し前にこのブログでも取り上げたredisを使うか悩んでるのですがどうしよう...

ちなみに僕は実際の開発でも、こんな感じでとりあえずハードコーディングしておいて後から関数メソッドの中を本来の形に実装する事があります。いっぺんに全てをちゃんと実装するより僕の場合はこっちのがやりやすいです。

2011-11-20

Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その4)

今回はURLから受け取ったパラメータをもとにJSONでレスポンスを返してみます。APIでよくあるパターンですね。
今回はmochijson2の使い方をさらっと見た上で前回のソースにちょっと追加します。

過去記事はこちら
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その2)
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その3)

mochijson2の使い方
僕自身がちゃんと解ってるか若干あやしいところがあるのですが...ともかくVoluntasさんの記事を見て色々やってみました、あと、mochiwebは各モジュールのソースに自身のテストコードが書いてあるので、それを見ると理解の助けになります。
Voluntasさんの記事「mochijson2 の使い方」
mochijson2のソースコード

例えば

[{"name", <<"shin">>}]

のようなデータをJSONに変換したければ

iolist_to_binary(mochijson2:encode({struct, [{"name", <<"shin">>}]})).
<<"{\"name\":\"shin\"}">>

こんな感じ。
もう少し複雑に

 [{"name", <<"shin">>}, 
  {"age", 36}, 
  {"sex", <<"male">>},
  {"lang", [<<"ruby">>, <<"objective-c">>, <<"erlang">>]}];

だと、

iolist_to_binary(mochijson2:encode({struct, [{"name", <<"shin">>}, 
                                                                    {"age", 36}, 
                                                                    {"sex", <<"male">>},
                                                                    {"lang", [<<"ruby">>, <<"objective-c">>, <<"erlang">>]}]})).

<<"{\"name\":\"shin\",\"age\":36,\"sex\":\"male\",\"lang\":[\"ruby\",\"objective-c\",\"erlang\"]}">>

てな具合で。

JSONでデータを返すサンプルコード
以上のmochijson2の使い方をふまえた上で前回のコードにJSONでレスポンスを返す関数を追加します。
http://localhost:9999/user/show/[ユーザ名]
のようなリクエストに対してそのユーザ情報をJSONで返します。

-module(user_action).

-include_lib("eunit/include/eunit.hrl").

-export([start/1, stop/0]).


start(Port) ->
    mochiweb_http:start([{port, Port}, {loop, fun dispatch/1}]).

stop()->
    mochiweb_http:stop().

%%%===================================================================
%%% Reqest Dispatcher
%%%===================================================================

dispatch(Req) ->
    Path = Req:get(path),
    Params = Req:parse_qs(),

    case string:tokens(Path, "/") of
        [Controller, Action | UrlParams] ->
            handle(Controller, Action, UrlParams, Params, Req);
        [Controller] ->
            handle(Controller, "index", [], Params, Req);
        [] ->
            handle("top", "index", [], [], Req)
    end.

%%%===================================================================
%%% Request Handlers
%%%===================================================================

handle("top", "index", _Path, _Params, Req) ->
    Req:ok({"text/html", "Welcome!"});

handle("user", "welcome", [Name], _Params, Req) ->
    Req:ok({"text/html", subst("Welcom(in url) ~s!", [Name])});

handle("user", "welcome", [], Params, Req) ->
    Name = proplists:get_value("name", Params),
    Req:ok({"text/html", subst("Welcom(from get) ~s!", [Name])});

%%今回追加分。JSONでレスポンスを返す。
handle("user", "show", [Name], _Params, Req) ->
    case get_user(Name) of
        [] -> 
            Req:not_found();
        User ->
            Json = iolist_to_binary(mochijson2:encode({struct, User})),
            Req:ok({"text/html", Json})
    end;

handle(_, _, _, _, Req) ->
    Req:not_found().

%%%===================================================================
%%% Internal Functions
%%%===================================================================

%% shin,tuna以外は空を返す。
get_user(Name) ->
    case Name of
        "shin" ->
            [{"name", <<"shin">>}, 
             {"age", 36}, 
             {"sex", <<"male">>},
             {"lang", [<<"ruby">>, <<"objective-c">>, <<"erlang">>]}];
        "tuna" ->
            [{"name", <<"tuna">>}, 
             {"age", 3}, 
             {"sex", <<"male">>},
             {"lang", [<<"cat-lang">>]}];
        _ ->
            []
    end.

subst(Template, Values) ->
    list_to_binary(lists:flatten(io_lib:fwrite(Template, Values))).


前回のソースコードJSONでレスポンスを返すコードを追加しました。
get_user/1は実運用ではリレーショナルデータベースとかMnesiaからデータを取得するのでしょうが、そこまでやると要点がぼやけるので今回はハードコーディングしてます。
あとはユーザが存在しなければ"404NotFound"を、存在すればそれをJSONにして返します。

結果
ではいつものようにErlangノードを起動してコンパイル、動作確認します。

$ erl -pa ../mochiweb/ebin
1> c(user_action).
{ok,user_action}

2> user_action:start(9999).
{ok,<0.41.0>}

ユーザshinの情報を取得
f:id:hiroe_orz17:20111121021121p:image

ユーザtunaの情報を取得
f:id:hiroe_orz17:20111121021202p:image

存在しないユーザの情報を取得して404NotFoundが帰る事を確認
f:id:hiroe_orz17:20111121021230p:image

所感
ここまででなんとかJSONでレスポンスを返せるようになりました。既にあるモジュールJSONAPIを被せるだけであれば、ここまでの内容でもなんとかなりそうです。
あとはMnesiaまたはなにかしらのキーバリューストアを使ったセッション管理とか認証とかありますのでゆっくり学習していこうと思います。だんだんと機能を追加していくのは習得してる実感があって楽しいですねw

Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その3)

今回は前回作ったモジュールにちょっと手を入れてパラメータをとれるようにしてみます。

過去記事はこちら
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻
Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その2)

たとえば次のようなURLリクエストをうけた場合に
http://localhost:9999/user/wolcome?name=shin

"name"というキーの値として"shin"を受け取れるようになると表現の幅が広がります。

さらにRailsなどでは
http://localhost:9999/user/wolcome/shin
のようなリクエストに対して三つ目のパス(この場合は"shin")をあらかじめルートを定義しておけば取得出来ますので、これもやってみます。

ソースは以下のようにしました。

-module(user_action).

-export([start/1, stop/0]).

start(Port) ->
    mochiweb_http:start([{port, Port}, {loop, fun dispatch/1}]).

stop()->
    mochiweb_http:stop().

%%%===================================================================
%%% Reqest Dispatcher
%%%===================================================================

dispatch(Req) ->
    Path = Req:get(path),
    Params = Req:parse_qs(),

    case string:tokens(Path, "/") of
        [Controller, Action | UrlParams] ->
            handle(Controller, Action, UrlParams, Params, Req);
        [Controller] ->
            handle(Controller, "index", [], Params, Req);
        [] ->
            handle("top", "index", [], [], Req)
    end.

%%%===================================================================
%%% Request Handlers
%%%===================================================================

handle("top", "index", _Path, _Params, Req) ->
    Req:ok({"text/html", "Welcome!"});

%%===================================================================
%% @doc
%% URLの一部としてnameを取得
%% example: http://localhost:9999/user/wolcome/shin
%%===================================================================
handle("user", "welcome", [Name], _Params, Req) ->
    Req:ok({"text/html", subst("Welcom(in url) ~s!", [Name])});

%%===================================================================
%% @doc
%% getパラメータとしてnameを取得
%% example: http://localhost:9999/user/wolcome?name=shin
%%===================================================================
handle("user", "welcome", [], Params, Req) ->
    Name = proplists:get_value("name", Params),
    Req:ok({"text/html", subst("Welcom(from get) ~s!", [Name])});

handle(_, _, _, _, Req) ->
    Req:not_found().

%%%===================================================================
%%% Internal Functions
%%%===================================================================

subst(Template, Values) ->
    list_to_binary(lists:flatten(io_lib:fwrite(Template, Values))).

今までと同じようにノードを起動してコンパイルします。mochiwebへのパスを追加するのを忘れずに...

$ erl -pa ../mochiweb/ebin
1>c(user_action).
{ok,user_action}

2> user_action:start(9999).
{ok,<0.38.0>}

ブラウザを開いてアクセスしてみます。
http://localhost:9999/user/welcome?name=shin
f:id:hiroe_orz17:20111120180737p:image

さらに
http://localhost:9999/user/welcome/shin
f:id:hiroe_orz17:20111120180837p:image

どうやらおkなようです。
大事な部分としては

Req:parse_qs().

パラメータが得られる事。そこから

proplists:get_value("name", Params).

で目的のキーに対する値が得られる部分です。

Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻(その2)

前回に引き続き、Erlang製のWebフレームワークMochiWebをもうちょっと触ってみます。
前回のソースでは、ブラウザからどのパスにアクセスしても同じ結果でした。

Erlang製のWebフレームワークMochiWebをさらっと触ってみたでござるの巻

例えば
http://localhost:9999/helo
でも
http://localhost:9999/helo/world?name=shin

でも結果は同じ。
そこで今回はURLによって異なる動作をするようにソースに手を加えてみようと思います。

パスの取得の確認
まずはパスの取得がどうなっているのか確認する為に前回と同じ場所で以下のようなソースを用意します。

-module(action).

-export([start/1, stop/0]).

start(Port) ->
    mochiweb_http:start([{port, Port}, {loop, fun dispatch/1}]).

stop()->
    mochiweb_http:stop().

dispatch(Req) ->
    Path = Req:get(path),
    Req:ok({"text/html", Path}).


あとはノード上でコンパイルして走らせて...

$ erl -pa ../mochiweb/ebin

1> c(action).
{ok,action}

2> action:start(9999).
{ok,<0.38.0>}

ブラウザアクセスしてみます。まずは http://localhost:9999
f:id:hiroe_orz17:20111120113452p:image
ふむ。パスが無い時は"/"のみですか。

では http://localhost:9999/helo だと?
f:id:hiroe_orz17:20111120113653p:image
なるほど。"/helo"らしい。

では http://localhost:9999/helo/world は?
f:id:hiroe_orz17:20111120113853p:image
ふむふむ。

では最後にパラメータを付加したらどうなる? http://localhost:9999/helo/world?name=shin
f:id:hiroe_orz17:20111120113922p:image
なる。パラメータは取り除いてくれるのですね。

ちょっと汎用化
というわけで、ちょっとだけ汎用化してみた。

-module(action).

-export([start/1, stop/0]).

start(Port) ->
    mochiweb_http:start([{port, Port}, {loop, fun dispatch/1}]).

stop()->
    mochiweb_http:stop().

%%%===================================================================
%%% Reqest Dispatcher
%%%===================================================================

dispatch(Req) ->
    Path = Req:get(path),

    case string:tokens(Path, "/") of
        [Controller, Action | UrlParams] ->
            handle(Controller, Action, UrlParams, Req);
        [Controller] ->
            handle(Controller, "index", [], Req);
        [] ->
            handle("top", "index", [], Req)
    end.

%%%===================================================================
%%% Request Handlers
%%%===================================================================

handle("top", "index", _, Req) ->
    Req:ok({"text/html", "Welcome!"});

handle("helo", "index", _, Req) ->
    Req:ok({"text/html", "hello! mochiweb beginner! :-)"});

handle("helo", "world", _, Req) ->
    Req:ok({"text/html", "hello! mochiweb beginner! from world! :-)"});

handle(UnknownC, UnknownA, _, Req) ->
    Req:respond({404, [{content_type, "text/plain"}], 
                 subst("Unknown Route ~s:~s", [UnknownC, UnknownA])}).

%%%===================================================================
%%% Local Functions
%%%===================================================================

subst(Template, Values) ->
    list_to_binary(lists:flatten(io_lib:fwrite(Template, Values))).

*このページの最後にReq:not_found()を使用した改良版をのせてます。

ブラウザ

アクセスするとそれぞれのコンテンツが表示されます(●ω●)

また、どの定義にもマッチしなかったら "handle(UnknownC, UnknownA, _, Req) " にマッチして404Unknownを返します。
http://localhost:9999/this/is/unknown
f:id:hiroe_orz17:20111120122415p:image

このへんのルーティングに関してはErlangのパターンマッチが助けてくれます。
関数定義の引数のところに変数名ではなくマッチする値そのものを置いて、それがら全てマッチする関数が実行されるので冗長なcase文や読み解きにくくなりがちなメタな方法をとらなくてもすんなり書けますね。

....パターンマッチかわいいよパターンマッチ(*´∀`)
次回はもうちょっと汎用化をすすめてみましょう(*´∀`)

追記(2011/11/20)
Voluntasさんより「req:not_found/1 があるよ」とのアドバイスがありましたのでmochiwebに含まれるmochiweb_request.erlを覗いてみると確かに404を返す為の関数がありました^^;

%% @spec not_found() -> response()
%% @doc Alias for <code>not_found([])</code>.
not_found() ->
    not_found([]).

%% @spec not_found(ExtraHeaders) -> response()
%% @doc Alias for <code>respond({404, [{"Content-Type", "text/plain"}
%% | ExtraHeaders], &lt;&lt;"Not found."&gt;&gt;})</code>.
not_found(ExtraHeaders) ->
    respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders],
             <<"Not found.">>}).

引数にヘッダーへの追加を持たせられるようですが、特に必要なければReq:not_found()を呼べば良さそうですね。
というわけでそれを適用したコードは以下のようになります。

-module(action).

-export([start/1, stop/0]).

start(Port) ->
    mochiweb_http:start([{port, Port}, {loop, fun dispatch/1}]).

stop()->
    mochiweb_http:stop().

%%%===================================================================
%%% Reqest Dispatcher
%%%===================================================================

dispatch(Req) ->
    Path = Req:get(path),

    case string:tokens(Path, "/") of
        [Controller, Action | UrlParams] ->
            handle(Controller, Action, UrlParams, Req);
        [Controller] ->
            handle(Controller, "index", [], Req);
        [] ->
            handle("top", "index", [], Req)
    end.

%%%===================================================================
%%% Request Handlers
%%%===================================================================

handle("top", "index", _, Req) ->
    Req:ok({"text/html", "Welcome!"});

handle("helo", "index", _, Req) ->
    Req:ok({"text/html", "hello! mochiweb beginner! :-)"});

handle("helo", "world", _, Req) ->
    Req:ok({"text/html", "hello! mochiweb beginner! from world! :-)"});

handle(_, _, _, Req) ->
    Req:not_found().

このほうが意図が明確でわかりやすいですね。