2011年5月20日金曜日

Google App EngineにC#プログラムからログインする方法

今回はGoogle App Engine(GAE)にC#プログラムからログインする方法を解説する。

GAEで特定のUrlに対して、特定のユーザのみにアクセスを許可する場合はapp.yamlで下記のように設定をすればよい。

app.yaml
- url: /do/something/
  script: something.py
  login: required

これでGAEアプリのユーザのみが上記Urlへのアクセスが可能となる。ユーザではない、または未ログインのものが上記UrlにアクセスするとGoogleアカウント(またはOpen Id)のログインページへとリダイレクトされる。そこでユーザ名とパスワードを入力してログインすれば上記のUrlへとリダイレクトバックされる。これがブラウザ上の動作であれば問題はないが、プログラム上から認証を必要とするUrlへとアクセスする場合は困る。

認証を必要とするGAEアプリのUrlに対してリクエストを行う場合は下記の手順で行う必要がある。

  1. ClientLoginサービスにログインしAuthトークンを取得する
  2. 取得したAuthトークンを利用しGAEアプリにログインしCookieを取得する
  3. 取得したCookieとともにHttpGETやらHttpPOSTを行う

各項目を順を追って説明する。

ClientLoginサービスにログインしAuthトークンを取得する
string GetAuth()
{
    var request = (HttpWebRequest)WebRequest.Create("http://www.google.com/accounts/ClientLogin");
    var content = "Email=test@gmail.com&Passwd=testpass&service=ah&accountType=HOSTED_OR_GOOGLE";
    var byteArray = Encoding.UTF8.GetBytes(content);
    request.ContentLength = byteArray.Length;
    request.ContentType = "application/x-www-form-urlencoded";
    request.Method = "POST";
    var dataStream = request.GetRequestStream();
    dataStream.Write(byteArray, 0, byteArray.Length);
    dataStream.Close();
    var response = (HttpWebResponse)request.GetResponse();
    var stream = response.GetResponseStream();
    var reader = new StreamReader(stream);
    var loginStuff = reader.ReadToEnd();
    reader.Close();

    var auth = loginStuff.Substring(loginStuff.IndexOf("Auth")).Replace("Auth=", "").TrimEnd('\n');
    return auth;
}
まずhttp://www.google.com/accounts/ClientLoginへと必要な情報をポストする。各項目の詳細な説明は下記を参照して欲しい。
ClientLogin for Installed Applications

今回は最終的にGAEアプリへとログインしたいのでserviceはahとなる。これがカレンダーであればclなどそれぞれに対応したものへと変わる。ログインに成功するとSID、LSIDとともにAuthが返却されるのでAuthのみ取得する。他の二つは不要。

取得したAuthトークンを利用しGAEアプリにログインしCookieを取得する
CookieContainer GetCookies(string auth)
{
    var cookies = new CookieContainer();
    var url = string.Format("http://test.appspot.com/_ah/login?auth={0}",
                            System.Web.HttpUtility.UrlEncode(auth));
    var request = (HttpWebRequest)WebRequest.Create(url);
    request.AllowAutoRedirect = false;
    request.CookieContainer = cookies;
    request.ContentType = "application/x-www-form-urlencoded";
    request.Method = "GET";
    var response = (HttpWebResponse)request.GetResponse();
    var stream = response.GetResponseStream();
    var reader = new StreamReader(stream);
    var result = reader.ReadToEnd(); 
    reader.Close();
    return cookies;
}
先ほど取得したAuthトークンを使用しGAEアプリへとログインする。実際にはhttp://test.appspot.comの部分をアクセスしたいGAEアプリのUrlへと変更して欲しい。ログインに成功するとACSIDというクッキーが返却されているはずだ。余談ながらGAEアプリへのログイン後、POSTではなくGETでよい場合は、下記のようにリクエストすれば指定のUrlへとリダイレクトされる。

http://test.appspot.com/_ah/login?auth={0}&continue=http://test.appspot.com/do/something/

ただ今回はPOSTでリクエストしたいのでAllowAutoRedirect=falseで自動リダイレクトを無効化している。


取得したCookieとともにHttpGETやらHttpPOSTを行う
void PostToGAE()
{
    var auth = GetAuth();
    var cookies = GetCookies(auth);

    var url = string.Format("http://test.appspot.com/do/something/");
    var content = "testvalue=test";
    var request = (HttpWebRequest)WebRequest.Create(url);
    request.KeepAlive = false;
    request.CookieContainer = cookies;
    var byteArray = Encoding.UTF8.GetBytes(content);
    request.ContentLength = byteArray.Length;
    request.ContentType = "application/x-www-form-urlencoded";
    request.Method = "POST";
    var dataStream = request.GetRequestStream();
    dataStream.Write(byteArray, 0, byteArray.Length);
    dataStream.Close();
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();
    var stream = response.GetResponseStream();
    var reader = new StreamReader(stream);
    var result = reader.ReadToEnd();
    reader.Close();
}
取得したCookieをそれ以降のリクエストと一緒に送ってやれば認証されたユーザとしてサーバサイドで認識される。

※見て分かると思うけれど、上記方法は中間でトラフィックを監視している人物がいた場合に、その人物がクッキーを使いまわすだけで簡単になりすましが可能となる。そのため繊細な情報などを扱う場合はhttpsを使用するのが望ましい。httpsを使用した場合は返却されるクッキー名がSACSIDとなる。


仕組みが分かれば至極単純なのが理解できたと思う。上記のコードは動作テスト用に組んだものなので例外処理などまったくの手付かずなので使用される場合は随時手を加えてもらいたい。


余談
私がこの動作テストを行ったときに正常に動作させるまで二日ほどかかってしまった。その要因はサーバサイドのハンドラーをDjango-nonrelで実装していたのだが、POSTリクエストがcsrf protectionに引っかかっていたせいだった。ずっとGAEの認証側の問題だと考えてそちらばかり追っかけていたので、Djangoのほうまで頭が回らなかった。気付いてしまえば単純な問題だけれど、はまるときははまる。仮にDjango-nonrelを使用する場合は忘れずにハンドラーへ@csrf_exemptを付与するようにしてほしい。

下記、はまっているときにstackoverflowへ質問した内容。
Programmatically login to google app engine c#

2011年5月19日木曜日

Google App Engineで開発してみた感想とか環境とか

Google App Engine(GAE)でガリガリと開発し始めて2週間ぐらいたったので、今回は開発の感想とか現在の開発環境とか。

発端~準備
チヌかかり釣りMEGAのSNS要素としてコミュニティ機能が欲しかったので、どうせデータのスケーリングを視野にいれるならGAEでやってみんべ、と思い立って始めたのが3月末。以前からPythonには興味があったのでついでに勉強しようとPythonを言語として選択。Pythonの勉強用にDive Into Python 3という大変すばらしい資料がネットに転がっていたのでそれでPython的なものを理解した。ただPythonは3と2で言語仕様の互換性が途絶しているため実際にGAEで開発するときはそこらへんを考慮しなくてはいけなかった(まぁ今までのところほとんど意識することはなかったのだけれども)。

で、Pythonをぼんやりと理解したので次いで背景も含めたGAEの理解に努めた。資料的にはGetting Started: PythonからApp Engine Python Overviewまわりの全部、それと内部的な動作を理解するのにMastering the datastoreもすべて目を通した。あとIndexまわりを理解するためにもBigtable: A Distributed Storage System for Structured Dataの出だし数ページには目を通しておいたほうが良いと感じた。

開発はじめ
開発環境はEclipse 3.6 Heliosを採用。GAEのページにEclipse用のプラグインがあるのでそれを使えば簡単に開発環境が構築できる。ただ、GAEそのままで開発を始めるとすぐに不便なことに突き当たる。GAEはDjangoのテンプレート機能を標準でサポートしてくれているが、それ以外の機能はない(それ以外にももしかしたらあるのかもしれないけれど知らない)。たとえばユーザ機能(GAE標準としてはGoogle AccountとOpen Idが用意されている)だったり、管理者機能、セッション機能なんかも当然ない。セッション機能とかはGAE用にgae-sessionsなどを開発してくれている人がいるけれどもどうせならここらへんの機能はフレームワークとして用意されているほうが好ましい。また生のGAEで困ったのがフォームの検証機能なんかも全然サポートされていない。これはかなり困った。ということでGAE上でDjangoを使用できるようにポートしてくれているDjango-nonrelを使用することにした。

Django-nonrelを使うことでModelFormやUser機能などDjangoの恩恵をすべて享受しているのに、さらにGAEのデータストアをラップしてくれているModel機能のお陰でBigtableも意識せずに使えちゃうというヘブン状態に突入できた。ただ当たり前なのだけれどもデータのjoinができないなどのGAEのデータ特性はそのままなのでそこらへんは注意しなければならない。

環境とか
Django-nonrelはほとんど素のDjangoと変わらずに開発できる(素のDjangoを触ったことがないので実際には分からないけど)のでストレスなくサクサク開発できるようになった。そこで更なる開発効率を達成するために下記のプラグインやらフレームワークやらを導入した。

Aptana Studio 3
これを入れるとPyDevが手に入り、さらにEclipse用のDjangoプラグインが手に入る。さらにさらにDjango Template Editorも手に入るのでマストゲットな一品。

Lettuce
Behavior Driven Development用のテストフレームワーク。これはかなり良い。C#用のMSpecとかよりも断然開発しやすい。MSpecが駄目というよりもDjango自体が自動テストで開発するのに向いてる環境だと実感した。

Selenium
ブラウザの挙動をそのままトレースできるツール。かなりのポテンシャルを感じるけれど、Lettuceとの相性がいまいちなのでいまいち把握していない。複雑なUIをぐりぐり作り始めたらちゃんと調査するつもり。

現在の開発環境は下記
Eclipse 3.6
PyDev
Django-nonrel
Lettuce

感想
GAEで開発しているという感じはほぼなく、Djangoで開発していると感じる。それと環境をしっかりと整えればかなり開発しやすいと感じた。まだまだTransaction周りなど、Django-nonrelがどのようにラップしているか正確に把握していないので手探りしながらなのは否めないけれど、大枠問題がない。あとPythonでの開発は楽しい。コンパイラが無いのはうっとうしいけれど、その分しっかりとテストコードを書いておけばいいのだし、もっとしっかりやるならコードカバレッジも併用すればよいんでしょ(まだ使ったことないけど)。

2011年5月5日木曜日

pipとかLettuceとかのセットアップ覚書 for Windows 7

Djangoアプリ用にBDDスタイルのテストフレームワークを探していたらLettuceがよさげだったのでインストールしてみた。その際に色々苦労したので今後のためのメモ。

まずpipをインストールする。
手順は下記を参考。
How to install pip on windows?

ついでLettuce。このTutorialどおりにpipを使ってのインストールから最初のテスト実行までを行う。
が、Lettuceを実行してみると表示がおかしい。本文以外に変な文字がたくさん出力される。


LettuceはWindowsに配慮されていないのでこういう悲しいことになっているようなのだが、それを解決する方法は下記の3. Fixing the Outputを参照のこと。
How to Install Lettuce in Windows

上記の方法を適用すると下記のように正しく表示される。


ここまででやっとスタートラインに立てた。でもまだDjangoと統合したり、Django-nonrelと一緒に使ってみたり、splinterも評価してみようかなーとか思ってるので道半ば。