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分)

アプリケーションのテスト(3)

はじめに

おはようございます。当ブログにアクセス頂き、ありがとうございます。夜6時以降は炭水化物を摂取しない、たなけんです。本エントリでは、アプリケーションのテスト(3)について記載します。

アプリケーションのテスト(3)

関数の機能を変更したため、テストコードも変更しました。
テスト事項は以下となります。
  • interface-for-clientのテスト
  • call-apiのテスト
  • 抽出関数のテスト
また、以下のテストデータを追加しました。
  • test/data/test-dictionary.xml
  • test/data/test-thesaurus.xml
  • test/data/test-slang.xml
  • test/data/test-etymology.xml
  • test/data/test-example.xml
  • test/data/test-questionanswer.xml
  • test/data/test-synonym.xml
  • test/data/test-random-dictionary.xml
  • test/data/test-random-thesaurus.xml
  • test/data/test-spelling.xml
  • test/data/test-wotd.xml

interface-for-clientのテスト


httpのGETリクエストからcall-apiを呼び出し、正常に処理されることを確認します。

call-apiのテスト


url、パラメータ及び抽出関数を引数に、call-apiが期待する動作をするかどうかを確認します。

抽出関数のテスト


与えられたxmlから、期待される値が抽出されることを確認します。
また期待される値を定数として定義します。

テストデータの登録


Dictionary.comの各APIにアクセスした際に取得されるxmlを定義します。

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

2012年7月14日土曜日

クライアント向けインターフェースの実装(2)

はじめに

おはようございます。当ブログにアクセス頂き、ありがとうございます。好きな言語は?と聞かれたら、Clojureと即答する、たなけんです。本エントリでは、プレゼンテーション層とアプリケーション層とをつなぐ、クライアント向けインターフェースの実装について記載します。

クライアント向けインターフェースの実装(2)

前回リストアップした以下の項目に従い、各関数を修正します。
  • interface-for-clientへのリクエストの処理を追加
  • 関数を引数に取り、抽出結果のJSON文字列を生成する関数の追加
  • 利用APIに対応した抽出関数の追加

interface-for-clientへのリクエストの処理


urlリソースと関数を関連付けます。例えば、http://localhost/dictionary/testへアクセスすると、引数のwordに"test"が拘束された状態で、call-apiが呼び出されます。

  • compojure.core/defroutesマクロ: urlリソースを定義
  • compojure.core/GETマクロ: http GETメソッドによりアクセスされる節を構成
  • call-api関数: url、パラメータリストおよび抽出関数を引数に取り、JSON文字列を生成


抽出結果のJSON文字列を生成する関数


抽出関数を引数に取り、Dictionary.comから取得したxmlから必要な情報を抽出した結果のJSON文字列を生成します。

  • ->マクロ: 左の関数を評価した結果を右の関数の引数とし、順次関数を実行
  • build-url-with-prms関数: urlパラメータのリストからurl文字列を生成
  • get-body関数: 引数のurlへhttpリクエストを送信し、そのhttpレスポンスのbody要素を取得
  • extract-function(引数): 関数呼び出し時に指定される抽出関数
  • clj-json.core/generate-string関数: clojureオブジェクトをJSON文字列に変換

抽出関数


xpathによりxmlからノードを取得し、さらにそこから指定した要素を抽出します。

  • safety-xpath-call関数: xpathによりxmlからノードを取得する。指定したxpathが取得出来なかった場合、handle-exception関数を呼び出す
  • get-result関数: safety-xpath-call関数により取得したxmlノード(ネストした状態のハッシュ
  • )から、順にキーを指定して、求める値を取得する
  • get-query-word関数: xml内に含まれる、問い合わせ単語を取得する。具体的にはxpathが(/*)[1]のノードから、キー:attrs :queryの値を取得する。
  • extract-dictionary関数: 英英辞書APIから取得したxmlから、問い合わせ単語と定義を抽出する。
  • extract-example関数: 例文辞書APIから取得したxmlから、問い合わせ単語と例文を抽出する。
  • extract-random関数: ランダムAPIから取得したxmlから、単語を抽出する。
  • extract-spelling関数: 綴り時APIから取得したxmlから、問い合わせ単語と綴り字の候補を抽出する。





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