Module::Spyでテンプレートに渡したパラメータのテストをする
概要
JSONを返すAPIのテストなら、スキーマの確認がやりやすいのだけど
テンプレを返すコントローラってどうやってやろうか、という課題。
テンプレを表示して目視する作戦は効率悪いし、絶対漏れるし
継続的にするの無理だし、プログラムのテストとテンプレのテストが
混ざっていてイケてない感。
IFで使用するパラメータを渡しているかが知りたいのであって
分岐した結果の表示は、また別問題だと思うのです。
で、実際にやってみたらできた!
ざっくりこんな感じ。
$spy = spy_on('Text::Xslate', 'render'); # コントローラ呼ぶ my $mech = Test::WWW::Mechanize::PSGI->new(app => $app); $dat = $mech->get('/'); is($dat->code. 200); my $tmpl = $spy->calls_first->[1]; my $tmpl_params = $spy->calls_first->[2]; is($tmpl, '/index.tt'); my ($ok, $error) = ThaiSchema::match_schema( $tmpl_params, type_hash({ member_id => type_int(), items => type_array( id => type_int(), name => type_str(), num => type_int(), ), pager => type_hash({ total_entries => type_int(), page => type_int(), hashNext => type_bool(), rows => type_int(), }) }); ); is_deeply($tmpl_params, $expected);
calls_firstの1番目以降に、renderメソッドに渡しているパラメータが入っているので
あとは煮るなり焼くなり、好きにできて嬉しい。
まとめ
なんかページの表示がおかしいって時に、コントローラのテストを行うことで
俺のせいじゃないです問題の切り分けが素早くできるようになるはず!
ajaxでクロスドメインのAPIを叩く時にやったこと
アプリのweb viiewから、ajaxでクロスドメインのAPIを実行しようとして
とても大変な思いをしたので備忘録。
概要
GETが失敗する件
ログを見ると、OPTIONSリクエストに403を返していた。
What is OPTIONSリクエスト?
プリフライトリクエストという。
リクエストを送信しても安全か?サーバーがリクエストに対応しているか?
ということを調べるために、ブラウザが特定の条件を満たす場合に飛ばす。
プリフライトリクエストに対応する
以下のモジュールを使った。
Plack::Middleware::CrossOrigin - search.cpan.org
app.psgi
enable 'Plack::Middleware::CrossOrigin', origins => '*', headers => '*', methods => ['GET', 'POST'];
JSONPに対応する
What is JSONP?
- JSでは、クロスドメインにアクセスできない制限がある(同一生成元ポリシー)
- クロスドメインからJSをダウンロードすることは可能である。
- よって、クロスドメインから、APIの戻り値が入ったスクリプトを取得することは可能である。
という小細工仕組み。
JSONPで公開しているリソースは、誰からでも参照できてしまうので
機密情報を入れると情報漏えいのリスクがあり、注意が必要。
以下のモジュールを使った。
Plack::Middleware::JSONP - search.cpan.org
app.psgi
enable 'Plack::Middleware::JSONP';
他の対策案を考える
POSTでGET
語感だけは楽しそうだけど、いけてない感。
GETするのをやめる
HTMLを返すときに、APIの結果を埋め込めばいいんだ!
そして、埋め込まれた結果を使って、JSでDOMを弄くろう!
(いま考えると、なぜテンプレートを使おうという発想にならなかったのか)
加工済みのテンプレートを返す対応
結局こうなった。
POSTが失敗する件
Androidだけまた動かない。
OPTIONSで200 OKを返しているが、ブラウザでabortしている。
ここは理由が分からなかったので課題。
OPTIONSを飛ばさないでPOSTする分には動作していたので、
カスタムヘッダを削る&Content-Typeに「application/x-www-form-urlencoded」を指定で対応。
Access-Controll-Allow-Origin:"*"ってどうなのか
全てのホストからのリクエストを許可する設定はよくないので、Originを指定したいが
Androidのweb viewはリクエストのOriginがnullになっているというやる気のなさ。
しょうがないので、特定のリクエストの時のみ、CORSヘッダを付与するようにした。
まとめ
動いたことに満足しがちだけど、クロスドメインに紐づくセキュリティリスクも
きちんと知ることが重要。
あと、Androidのweb viewでクロスドメインは罠が非常に多かったので
jsをAPIと同じホストに置くのが一番良いのではないかと思う。
参考
http://www.w3.org/TR/cors/
https://developer.mozilla.org/ja/docs/HTTP_access_control
ここを熟読して、CORSとはなんなのかをよく学ぶべき。
http://www.atmarkit.co.jp/ait/articles/0908/10/news087.html
JSONPを使用するまえに熟読して、リスクを知るべき。
クロスドメインで調べた事メモ
ajaxでクロスドメインだと動かない件で、ググったことが多かったのでメモ。
じゃあどうすれば?
JSONP
JSONPの仕組み
- javasciprtの読み込みは別ドメインからできる。
- なのでjavascriptを読み込む体で、HTTPリクエストする。リクエストにはcallbackってパラメータいる
- そしたらjavascriptのコードが返ってくる(おや?偶然だけど中身はJSON!)
request例
/api/item/detail?callback=jsonp&id=100
response例
content-Type: text/javascript
content: jsonp({"result":{"item_id":100, "item_name":'アイテム'}})
plackアプリで実装するとき
PLACK::Middleware::JSONPを使う
パラメータに'callback'があれば、自動的にjsonを上記の形にして
sizeとかcontent-Typeを修正してくれる。
JSONとJSONPの対応を、モジュールに手を入れずにできる。
JSONPの課題
- ブラウザ依存ある
XMLHttpRequest Level2に対応したブラウザじゃないとダメ。
IE7とかはさようなら。。
- コードの精査はできない。
危険なコードも制御できずに実行される。
- GETはできるけどPOSTできない。
ajaxで商品の参照はできるけど、購入は無理。
user-agentでOSとか判定するのは辛かった
user-agentを使用して、OSとかブラウザとか判定する実装した。
ようは、動作環境を満たすかチェックしたかったので。
吐きそうなくらい辛かった気持ちを忘れないためにメモを残す。
まずはCPANをチェック
HTML::ParseBrowser - search.cpan.org
[OK] OSのバージョン取れる。
[OK] ブラウザ名取れる。
[NG] スマホかどうかは不明。
[NG] タブレットかは分からない。
Parse::HTTP::UserAgent - search.cpan.org
[OK] OSのバージョン取れる。
[OK] ブラウザ名取れる。
[NG] スマホかどうかは不明。
[NG] タブレットかは分からない。
HTTP::UserAgentStringParser - search.cpan.org
そもそも外部と通信する必要あるのが懸念材料。
Woothee - search.cpan.org
[NG] OSのバージョン取れない。
[OK] ブラウザ名取れる。
[OK] スマホかどうか分かる。
[NG] タブレットかは分からない。
検討したこと
CPANモジュールはどれも少しずつ惜しい。
そしてアプリケーションで利用するためには、
windowsは7も8もwindowsとして管理したり、'mac'という文字列じゃなくて定数にしたかったり
様々な加工が必要だったので、泥臭く実装することにした。
面倒だったポイント
実装されたもの(いろいろ出していけない部分は削っている
感想
もうUserAgentで判定する世界はなくなればいいのに。
好き勝手に文字列作りすぎ。
perlの文字列をバイト数で切り取るヤツ
APIに渡す文字列は25文字(50byte)でよろしく、
という要件に対応するサブルーチンを実装した時のメモ。
サブルーチンでは以下の3つを考慮する。
・文字数制限を満たす
・バイト数制限を満たす
・文字列として成立する(単純にバイト数でぶった切ると、文字列がおかしくなる
実装の時に調べてしったこと
・bytesプラグラマは非推奨
http://perldoc.perl.org/bytes.html
雑感
文字列は入り口でデコードして、出口でエンコードなんだから、
encodeしてlengthを取るのはごく普通なことだと思った。
あと、文字数がバイト数より大きいくなることってありえるのかなぁ。
よくて同じな気がする。
そうだったら、文字列の長さチェックは省略してもよいのかもしれぬ。
所有ユーザーとグループを同時に変更する(しかも再帰的に
git pullしたらpermissionがどうとか言われて怒られた。
以前rootでgit pullやってしまった記憶がなきにしもあらずなので
指摘されているファイルの権限を修正した。
表示されたエラー
remote: Counting objects: 120, done.
remote: Compressing objects: 100% (95/95), done.
remote: Total 98 (delta 75), reused 0 (delta 0)
error: insufficient permission for adding an object to repository database .git/objects
fatal: failed to write object
fatal: unpack-objects failed
対処のために打ったコマンド
chown -R user:user /home/project/.git/objects
たまにしか使わないから毎回ググってるけど、そろそろ覚えよう。