2012年7月29日日曜日

Webクライアントの実装(3)

はじめに

おはようございます。当ブログにアクセス頂き、ありがとうございます。
鳥のさえずりで、毎日優雅に目覚めている、たなけんです。(私の住んでいるキャンベラは人よりも大型の鳥の方が数が多いようで、叫び声に似た鳴き声で毎日起こされています。)
本エントリでは、Clojurescript(というかClosure Library)でのログ出力の方法および、これまで作成したWebクライアントの、ロジックの分離について記載します。

ログ出力の設定

Closure Libraryではログ出力の為のクラスを提供しており、それをインスタンス化するだけでログを出力することができます。
また、ログの出力先をコンソールにするなど、コンソール環境を整えるクラスのスタティックメソッドを用いて、ログ出力環境を設定することができます。
コードは、本エントリ末尾に掲載します。

ロジックの分離

ロジック分離前のソースコードでは、サーバから渡されたデータをさらにgoogle visualization apiのDataTableクラス用に加工していました。このデータ変換は、クライアント側で行う必要性が薄いため、サーバ側でクライアントが必要するデータの形式に変換するようにしました。
また、clj->js関数については非常に汎用性が高い関数(標準ライブラリに入っていても良さそう)であるため、名前空間my-dictionary-client.utilに移動しました。

ロジック分離後のソースコード(クライアント)


ログの出力設定後、google visualization apiを読み込み、読み込み後にinit関数を呼び出します。init関数でdictionary-button要素のonClickイベントとdictionary-request関数を結びつけ、ボタン押下時にサーバと通信し、結果を表示します。
ロジックを可能な限りサーバ側へ寄せたことで、クライアント側での処理をライブラリの利用準備とユーザの操作への応答のみに限定することができました。
サーバおよびクライアントの全ソースコードは、githubにて公開していますので、関心のある方は是非ご覧になって下さい。

今回の作業は以上。最後までお読みいただきありがとうございました。
たなけん(作業時間30分)

Clojurescriptでrepl

はじめに

おはようございます。当ブログにアクセス頂き、ありがとうございます。
Clojurescriptの深みに日々はまっている、たなけんです。
本エントリでは、Clojurescriptでのrepl使用方法を記載します。

Clojurescriptのreplの種類

Clojurescriptでは、以下の2種類のreplを利用することができます。

  • Rihno環境
  • Webブラウザ環境

今回は、ブラウザ上でのClojurescriptの挙動を確認するため、後者のWebブラウザ環境でのreplの使用方法について記載します。
Webブラウザ環境のreplでは、cljsbuildプロセスが入出力を処理し、Webブラウザプロセスが入力されたS式を評価します。

cljsbuildによるrepl利用の準備

cljsbuildはコンパイルだけではなく、replの起動、テストの実行といった機能も兼ね備えています。しかし、Webブラウザ環境でreplを起動するには、接続用のhtmlを読み込むClojurescriptおよび接続用のClojurescriptが必要となります。今回はClojurescriptのwikiを例として取り上げますが、実際に開発しているアプリケーションに、数行付け加えるだけでも、Webブラウザ環境のreplを使用することができます。
下記で示される、コードはClojurescriptのrepl利用の手引きからの引用となります。


htmlファイル


何の変哲も無い、ただ接続用のClojurescriptを読み込むだけのhtmlです。
しかし、1点だけ注意事項があります。それは、必ずボディ部でscript要素を宣言しなくてはいけないということです。
私は当初ヘッド部でJavascriptライブラリの読み込みを宣言しており"Type error: parentElm is null"というエラーに悩まされてしまいました。

接続用のClojurescript


clojure.browser.replを利用し、cljsbuildで起動したポートへ接続します。
clojure.browser.repl/connect関数: 引数のurlへ接続し、replへの入力を受け付ける

起動から接続まで

以下の手順によりreplを起動し、入出力プロセスと評価プロセスを接続します。

  1. コンソールを起動、cljsbuildを利用しているプロジェクトのルートへ移動
  2. コンソールから、lein trampoline cljsbuild repl-listenと入力し、replを起動
  3. Webブラウザを起動し、接続用htmlを読み込む
  4. 同じWebブラウザからurl(デフォルトlocalhost:9000)へアクセスし、cljsbuildのreplと、Webブラウザの接続を確立(接続用のhtmlファイル名がindex.htmlではない場合(例. not-index.html)、urlはhttp://localhost:9000/not-index.htmlのようになる)

replの利用

接続が確立された後は、コンソールから評価したい式を入力することで、ブラウザのdom要素等を操作することができます。
また、load-namespace関数を使うことで、クラスパス内の任意のClojurescriptファイルを読み込むことができます。

今回の作業は以上。最後までお読み頂きありがとうございました。
たなけん(作業時間45分)

2012年7月27日金曜日

Webクライアントの実装(2)

はじめに

おはようございます。当ブログにアクセス頂き、ありがとうございます。
新学期が始まり、そろそろ会計とマネジメントの授業に集中せねばとプレッシャーを感じている、たなけんです。
本エントリでは、Google visualization APIを利用したUIの実装について記載します。

Webクライアントの実装(2)

Closure Libraryでは多彩なUIコンポーネントが用意されています。しかし、Closure LibraryのUIコンポーネントはややオーバースペックであるため、Google visualization APIを利用し、表形式で結果を表示したいと思います。

htmlへの追記


Google jsapiを利用するため、下記の内容をindex.htmlのヘッダ部に追記しました。

データの変換


JSONをClojureオブジェクトへ変換する関数です。
semperosnのgistで公開されていた関数をコピーさせて頂きました。

google visualization API読み込み


ライブラリを読み込み、読み込み後にinit関数を実行するよう設定します。

  • google/loadメソッド: ライブラリを読み込む
  • google/setOnLoadCallbackメソッド: ライブラリ読み込み後に実装する関数を指定


表データの作成


サーバから返された値から、google.visualization.Tableクラスのコンストラクタが要求する形式のデータを作成します。

表の描画


div要素resultに表を描画します。
  • js/クラス名 特殊形式: 指定されたクラスをJavaScriptのオブジェクトとして利用する
  • google.visualization.Tableクラス: テーブルを描画するクラス。引数に、描画する位置となる要素を指定する
  • google.visualization.DataTableクラス: データを司るクラス。引数にデータとなるJSONを指定する

今回の作業は以上。最後までお読み頂きありがとうございました。
たなけん(作業時間45分)

2012年7月25日水曜日

クライアント/サーバ間通信でのトラブル

はじめに

おはようございます。当ブログにアクセス頂き、ありがとうございます。
最近になってビリーズブートキャンプに入隊した、たなけんです。
本エントリでは、クライアント/サーバ間通信でのトラブルとその解決方法について記載します。

現象の整理

Webクライアントからサーバにアクセスした際、あるブラウザ(Safari)では期待通りの結果が表示されるが、他のブラウザ(Chrome、Firefox)では期待通りの結果が表示されませんでした。
表示されない原因として、サーバから返されるhttpレスポンスのボディ部に、ブラウザ毎で下記の違いがありました。

  • Safari: httpレスポンスのボディ部にサーバから返されたデータが設定されている
  • Chrome & Firefox: httpレスポンスのボディ部に何も設定されていない


クライアントを疑う

ClojurescriptでのWebクライアントの実装は初めてだったため、もしかしたら何かバグを踏んでいるのではないかと思い、極力シンプルな2つのテストを実施しました。

  • ブラウザから直接アクセス: すべてのブラウザで期待した結果が得られた(リクエストのボディ部に設定したJSON文字列が表示された)
  • jQueryを用いたAjaxクライアントの利用: Clojurescriptと同様、Safariと他のブラウザで異なる結果となった

クライアントコード

これらの結果から、問題はクライアントの実装ではないと判断しました。

サーバ側を変更してみる

Compojureとブラウザ側のXmlHttpRequestとの相性問題などの可能性を考え、2つの異なるhttpサーバを用いたコードを作成し、各Webブラウザより先述したクライアント(jQuery版)からアクセスしたところ、下記の様な結果となりました。

  • Bishop (clojure/ringベースの別のwebサーバ(Web Machineクローンで、Webサービス提供用に機能を特化している)): Clojurescriptと同様、Safariと他のブラウザで異なる結果となった
  • restify (node.js上で動作するWebサーバ用ライブラリ): すべてのブラウザで期待した結果が得られた

Compojure同様clojure/ringベースの別のwebサーバであるBishopを用いても問題は解決せず、node.jsベースのrestifyでは問題なく動作することが分かりました。

トラブルの原因と解決方法

ブラウザからローカルのファイルにアクセスする場合には、ブラウザの起動オプション等を変更する必要があるという様な話は以前より知っていましたが、今回の現象はローカルのWebサーバへのアクセスなので、ブラウザの起動オプションを変更しても問題は解決されませんでした。
通常の開発と今回のとの相違として、httpサーバにアクセスしてindex.htmlを取得し、それをブラウザから操作するのではなく、ローカルで作成したindex.htmlをブラウザから直接開いて操作しているという点に思い至りました。つまり、サーバとしては自分がホストしているドメインとは異なるドメインからアクセスを受けたと認識している訳です。
そこで、外部ドメインからのアクセスを許可するようサーバ側のコードを変更しました。具体的にはhttpヘッダに"Access-Control-Allow-Origin" "*"を追加するよう改修しました。
改修の結果ChromeおよびFirefoxでも期待通りの結果を得ることができました。
今後クライアントサイドMVCフレームワークの流行等で、サーバとクライアントを切り離して開発することも増えるかと思います。その際、同様の問題が発生した場合には、サーバ側でAccess-Control-Allow-Originが適切に設定されているかどうかを確認すると良いという貴重な教訓を得ることができました。


今回の作業は以上。最後までお読み頂きありがとうございました。
たなけん(作業時間120分(トラブルシュート含む))

Webクライアントの実装(1)

はじめに

おはようございます。当ブログにアクセス頂き、ありがとうございます。
昨日美味しい生牡蠣を腹一杯食しご機嫌の、たなけんです。
本エントリでは、Clojurescriptを利用したWebクライアント開発について記載します。

Clojurescriptとは

Clojurescriptとは、ClojureでJavaScriptアプリケーションを開発するためのライブラリ群です。CoffeeScriptやJSX同様、書かれたプログラムをJavaScriptにコンパイルすることによって、アプリケーションをJavaScriptが動作する環境(webブラウザやnode.jsなどのサーバ上)で動作させることができます。

なぜClojurescriptなのか

JavaScriptの言語仕様(変数スコープ、プロトタイプ方式オブジェクト指向など)に馴染めないこと、Clojureスタイルでのプログラミングが私にとって最も効率が良いということからClojurescriptを利用することにしました。
また、ClojurescriptはGoogle Closure Toolsを利用しているため、Closureが提供するクラス方式のオブジェクトシステムや、多彩なUIコンポーネントが利用できる点も魅力でした。

Clojurescriptの導入

ひなたねこさんの記事を参考にlein-cljsbuildを導入しました。lein cljsbuild auto としておけば、core.cljsを更新したタイミングでJavaScriptにコンパイルされます。(他のツールは必要なく、lein-cljsbuildだけで完結している点が素晴らしいです)
また、emacsのClojurescript-modeはELPA経由でインストールしました。
Clojurescript用のreplもemacsから利用可能な様でしたが、今回は出力されたJavaScriptを見てデバッグを行いました。(時間を見つけて、Clojurescriptのreplも試そうと思います)

Webクライアントの実装(1)

プロジェクトファイル


プラグイン部でlein-cljsbuildの利用を宣言します。また、closure compilerで用いるコンパイルオプションをcljsbuild部で指定します。今回は生成されるJavascriptの可読性を重視したため、最適化はしない方針としました。

index.html


ヘッダ部でJavaScriptファイルを指定し、ボディ部の最後でmain関数を呼び出しています。また検索語を入力するフォーム、確定ボタン、結果表示用のdiv要素を宣言しました。

nsマクロ


通常のClojureとの違いとして、ClojureからJavaのクラスを利用する場合は:requireではなく:importを使用するのですが、ClojurescriptからClosure Libraryのクラスを利用する場合もClojureのライブラリ同様:requireで良いという点があります。
nsマクロでは、以下のライブラリの利用を宣言しました。
  • clojure.browser.event: ブラウザのイベントを操作するClosure Libraryのクラス群の薄いラップしたライブラリ
  • clojure.browser.dom: ブラウザのDOMを操作するClosure Libraryのクラス群の薄いラップしたライブラリ
  • goog.net.XhrIo: Closure Libraryが提供するJacascriptのhttpXmlRequestオブジェクトを操作するクラス


main関数


確定ボタンのクリックイベントをトリガーにdictionary-request関数を呼びます。

  • clojure.browser.event/listen関数: 引数に指定された要素、イベントによって第3引数で指定された関数が呼び出されるよう設定
  • clojure.browser.dom/get-element関数: 引数に指定されたシンボルのidに合致する要素を取得

生成されたJavaScript


dictionary-request関数


httpのGETメソッドによるリクエストを送信し、結果を引数にコールバック関数を呼び出します。
  • goog.net.XhrIo/sendメソッド: 引数のurlにhttpのGETメソッドによるリクエストを送信
  • clojure.browser.dom/get-value関数: 引数に指定された要素の値(今回の場合、フォームquery-wordに入力された文字列)を取得
生成されたJavaScript


dictionary-callback関数


httpレスポンスからJSONオブジェクトを取得し、create-list関数を用いて結果を変換し、変換された結果をdiv要素の子要素として追加します。
  • .-プロパティ名 特殊形式: JavaScriptオブジェクトのプロパティにアクセス(今回の場合entriesプロパティ)
  • goog.net.XhrIo/getResponseJsonメソッド: 取得されたレスポンス内のデータをJSONオブジェクトとして取得
  • clojure.browser.dom/append関数: 第1引数に指定された要素に、第2引数に指定された要素を子要素として追加
生成されたJavaScript

create-list関数


JSONオブジェクトを引数に取り、textプロパティから値を取得し、<ul><li>textプロパティの値</li> ... </ul>形式のdom要素を作成します。

  • clojure.browser.dom/html->dom関数: html形式の文字列からDOMツリーを生成


生成されたJavaScript

意外な落とし穴

実装を終えて早速クライアントからサーバへの疎通を確認しようと、ブラウザからアクセスしたところ、Safariでは問題なくレスポンスが処理されるが、Firefoxではレスポンスのボディ部が0バイト(サーバ側で設定したはずの値が、ボディ部に含まれていない)様な挙動を見せ、想定した通りの処理をすることができませんでした。
ブラウザ毎に異なる挙動はClosureライブラリで吸収されているはず、と思いながらも、ここから問題解決まで思ったよりも時間が掛かってしまいました。。。(次回に続く)


今回の作業は以上。最後までお読み頂きありがとうございました。
たなけん (作業時間90分)

2012年7月23日月曜日

第2イテレーションの計画

はじめに

おはようございます。当ブログにアクセス頂き、ありがとうございます。
『今やる、すぐやる、出来るまでやる』がモットーの、たなけんです。
本エントリでは、第2イテレーションの計画として、仕様および実装方針を記載します。


第2イテレーション(プレゼンテーション層/Webクライアント)の仕様

第2イテレーションで実装する機能の仕様は以下の通り。
  • 定義を取得したい単語をhttpリクエストを送信する
  • 外部APIからの抽出結果をhttpレスポンスとして受信する
  • 受信したデータを整形して

実装方針

  • クライアントはWebブラウザとし、Ajax通信によりサーバへ問い合わせる
  • クライアントプログラムの実装には、Clojurescript / Google Closure Libraryを用いる
  • CSSの作成にSCSS(Sass)を用いる

テスト方針

  • JsUnitを用いて単体テストを実施する
  • PhantomJSを用いて単体テストを自動化する
  • Seleniumにより結合テストを実施する


今回の作業は以上。最後までお読み頂きありがとうございました。
たなけん (作業時間10分)

2012年7月15日日曜日

第1イテレーションの振り返り

はじめに

おはようございます。当ブログにアクセス頂き、ありがとうございます。、たなけんです。本エントリでは、第1イテレーションを振り返ります。


第1イテレーションの振り返り

普段はツール開発にClojureを利用していましたが。今回のアプリケーション開発では、堅牢なアプリケーション開発のために以下の点を意識しました。

  • ライブラリの利用
  • ツールの利用
  • 実装


ライブラリの利用

多数のライブラリを利用しましたが、その中でも下記のライブラリについて利用した感想を簡単に紹介したいと思います。

  • clojure.tools.logging: ログ出力。副作用のある処理なのでdoと合わせて使うと便利
  • clj-xpath: xpath問い合わせ。完結かつ柔軟にxmlノードを抽出できて便利
  • clojure.test: テストコード定義。Leiningenからもテストが実行可能で便利


ツールの利用

実際にツールを利用した感想を以下に記します。

  • IDE: emacs。起動、終了が高速かつ、replを利用しながらテスト可能でありEclipceよりも使い勝手が良い印象
  • バージョン管理: git。特別な利用をしていないためSubversionなどと同様な印象
  • 継続的インテグレーション: Jenkins。まだ運用方法が未確立



実装

実装時に様々なClojureの機能や関数を利用しましたが、その中でも便利だったものを以下に紹介します。


  • 例外処理: gen-classを使用。Exceptionを継承することでアプリケーション例外を定義
  • 関数の公開範囲: 他の名前空間から呼ばれない関数はdefn-で定義
  • reduce関数: ネストされたハッシュから値を抽出する際に利用




今回の作業は以上。最後までお読み頂きありがとうございました。
たなけん (作業時間30分)