March 20, 2009

Android hack-a-thon

3/20に Android hack-a-thon に参加してきました。

みなさん一日で色々作っています。すごいな〜

私の方は、全くの初心者なので午前中は HelloWorld をやってみました。午後からは gdata のライブラリを使って Google Apps のグループアドレスを作成できるようなアプリケーションにトライ。

gdata の jar をプロジェクトに取り込み、必要な画面を作るところまではうまくいったけど、 GroupService の認証をするところでうまくいかず、当日はここで断念。原因はSSL証明書の検証が失敗している事まではつきとめたんですけど...

せっかくなので今日がんばって動かす事ができたので書きます。

まず gdata の Java クライアントのソースを落としてきて、下記のパッチを当てます。内容はSSL証明書の検証が必ず成功するようにしているだけです。


diff -uNr gdata/java/src/com/google/gdata/client/GoogleAuthTokenFactory.java gdata.mine/java/src/com/google/gdata/client/GoogleAuthTokenFactory.java
--- gdata/java/src/com/google/gdata/client/GoogleAuthTokenFactory.java 2009-02-12 05:09:34.000000000 +0900
+++ gdata.mine/java/src/com/google/gdata/client/GoogleAuthTokenFactory.java 2009-03-22 13:49:42.000000000 +0900
@@ -16,6 +16,13 @@

package com.google.gdata.client;

+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import javax.security.cert.X509Certificate;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLException;
+
import com.google.gdata.util.common.base.CharEscapers;
import com.google.gdata.util.common.base.StringUtil;
import com.google.gdata.client.GoogleService.AccountDeletedException;
@@ -450,7 +457,27 @@

// Open connection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
-
+ if ( urlConnection instanceof HttpsURLConnection ) {
+ HttpsURLConnection ucs = (HttpsURLConnection) urlConnection;
+ ucs.setHostnameVerifier( new X509HostnameVerifier(){
+ @Override
+ public boolean verify(String arg0, SSLSession arg1) {
+ return true;
+ }
+ @Override
+ public void verify(String arg0, SSLSocket arg1) throws IOException {
+ }
+ @Override
+ public void verify(String arg0,
+ java.security.cert.X509Certificate arg1)
+ throws SSLException {
+ }
+ @Override
+ public void verify(String arg0, String[] arg1, String[]
+ arg2) throws SSLException {
+ }
+ });
+ }
// Set properties of the connection
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
diff -uNr gdata/java/src/com/google/gdata/client/appsforyourdomain/AppsGroupsService.java gdata.mine/java/src/com/google/gdata/client/appsforyourdomain/AppsGroupsService.java
--- gdata/java/src/com/google/gdata/client/appsforyourdomain/AppsGroupsService.java 2009-02-12 05:09:34.000000000 +0900
+++ gdata.mine/java/src/com/google/gdata/client/appsforyourdomain/AppsGroupsService.java 2009-03-22 14:10:47.000000000 +0900
@@ -37,7 +37,7 @@
public class AppsGroupsService extends AppsPropertyService {

public static final String BASE_URL =
- "http://apps-apis.google.com/a/feeds/group/2.0/";
+ "https://apps-apis.google.com/a/feeds/group/2.0/";
public final String baseDomainUrl;

public static final String APPS_PROP_GROUP_ID = "groupId";
diff -uNr gdata/java/src/com/google/gdata/client/http/HttpGDataRequest.java gdata.mine/java/src/com/google/gdata/client/http/HttpGDataRequest.java
--- gdata/java/src/com/google/gdata/client/http/HttpGDataRequest.java 2009-02-12 05:09:35.000000000 +0900
+++ gdata.mine/java/src/com/google/gdata/client/http/HttpGDataRequest.java 2009-03-22 13:49:39.000000000 +0900
@@ -16,6 +16,13 @@

package com.google.gdata.client.http;

+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import javax.security.cert.X509Certificate;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLException;
+
import com.google.gdata.util.common.xml.XmlWriter;
import com.google.gdata.client.AuthTokenFactory;
import com.google.gdata.client.GDataProtocol;
@@ -312,6 +319,29 @@
}
HttpURLConnection uc = (HttpURLConnection) requestUrl.openConnection();

+ // Open connection
+ if ( uc instanceof HttpsURLConnection ) {
+ HttpsURLConnection ucs = (HttpsURLConnection) uc;
+ ucs.setHostnameVerifier( new X509HostnameVerifier(){
+ @Override
+ public boolean verify(String arg0, SSLSession arg1) {
+ return true;
+ }
+ @Override
+ public void verify(String arg0, SSLSocket arg1) throws IOException {
+ }
+ @Override
+ public void verify(String arg0,
+ java.security.cert.X509Certificate arg1)
+ throws SSLException {
+ }
+ @Override
+ public void verify(String arg0, String[] arg1, String[]
+ arg2) throws SSLException {
+ }
+ });
+ }
+
// Should never cache GData requests/responses
uc.setUseCaches(false);






あと gdata/java/build-src/core.xml のコンパイル箇所で android.jar に classpath を通せばコンパイルが通ります。(メッセージに従って mail.jar と activation.jar を用意する必要もあるけど)

でき上がった gdata-appsforyourdomain-1.0.jar と gdata-core-1.0.jar を使うときちんと動かすことができました。手軽に Google Apps の Group を追加できるだけのしょーもないアプリですが...



これを進化させて Google Apps の管理コンソールが作れそう。でも需要は無さそうだなorz

March 5, 2009

Werkzeug の easteregg



僕も最近話題の Werkzeug を使ってみようと思いました。しかしまだ使うまでにいたらず、とりあえず easteregg を発見して喜んでいる状態です。

February 23, 2009

Gaebarを使ってみた


先日 App Gallery という GAE 上のサービスを始めてみました。サービスをやるからにはデータをバックアップしたいと思い、Gaebar を試してみたのでご紹介します。

なんと Gaebar の画面は django pony です!

Gaebar は Google App Engine Backup And Restore の略で、文字通り Google App Engine 用のバックアップ・リストアツールです。ゲイバーと読むそうです。この名前が良いですねw

Gaebar は Django のアプリケーションで簡単に使えます。GAE上で動く既存の Django アプリケーションにプラグインする事で、バックアップ・リストア機能を追加する事ができます。

残念ながら今は Django で動いているアプリケーションでしか使えませんが、少し頑張れば同じ事は別のフレームワークでも可能だと思います。

準備する

まずは Gaebar をダウンロードします。
Gaebar github から Download 機能を使ってアーカイブをダウンロードするか、下記のように git で clone しても良いです。

$ git clone git://github.com/aral/gaebar.git

またはあなたのプロジェクトが git を使っているなら下記のコマンドが良いかもしれません。

$ git submodule add git://github.com/aral/gaebar.git


方法はともあれ、プロジェクト直下に gaebar というディレクトリができていれば大丈夫です。

次に GAE の SDK にパッチを当てます。これは Gaebar がデータを python のファイルとしてダウンロードしてきてローカルのファイルシステムに保存するために必要なのです。方法が AralのBlog Entry に乗っていますが、この情報は少し古いので、SDK-1.1.9では下記のパッチを使ってください。

--- ../google_appengine/google/appengine/tools/dev_appserver.py.org 2009-02-26 11:33:33.000000000 +0900
+++ ../google_appengine/google/appengine/tools/dev_appserver.py 2009-02-20 17:04:46.000000000 +0900
@@ -1274,6 +1274,7 @@
allowed_symbols = self._WHITE_LIST_PARTIAL_MODULES[module.__name__]
for symbol in set(module.__dict__) - set(allowed_symbols):
if not (symbol.startswith('__') and symbol.endswith('__')):
+ module.__dict__['old_'+symbol] = module.__dict__[symbol]
del module.__dict__[symbol]

if module.__name__ in self._MODULE_OVERRIDES:


注意) app-engine-patch を使う場合は、settings.py で DJANGO_STYLE_MODEL_KIND = False にしないと動きませんでした。(少なくとも私は動かせませんでした。)動かせた方がいたら教えてください :-)

1. settings.py は下記のようにします。

INSTALLED_APPS = (
# Other apps...
'gaebar',
)


2. urls.py は下記のとおりです

urlpatterns = patterns('',

# ...other URLs

url(r'^gaebar/', include('gaebar.urls')),
)


3. app.yaml に static folder を追加します。

# Static: Gaebar
- url: /gaebar/static
static_dir: gaebar/static


4. index.yaml にインデックスを追加します。一回 dev server で Gaebar を動かせば自動で追加されます。

- kind: GaebarCodeShard
properties:
- name: backup
- name: created_at


5. Gaebar の設定を settings.py に追加します。

GAEBAR_LOCAL_URL: ローカル開発サーバの絶対 URL を書きます。
GAEBAR_SECRET_KEY: 秘密の文字列を記入します。誰にも秘密にしてください。
GAEBAR_SERVERS: dict でサーバーを列挙します。
GAEBAR_MODELS: tuple でバックアップするモデルを列挙します。

App Gallery で使用している設定のサンプルを下記に掲載します。

GAEBAR_LOCAL_URL = 'http://localhost:8000'
GAEBAR_SECRET_KEY = 'replace_with_your_secret_key'

GAEBAR_SERVERS = {
u'Deployment': u'http://app-gallery.appspot.com',
u'Staging': u'http://app-gallery-staging.appspot.com',
u'Local Test': u'http://localhost:8080',
}

GAEBAR_MODELS = (
(
'appgallery.models',
(u'Application', u'ThumbnailImage'),
),
(
'tags.models',
(u'Tag', u'TagApplication')
),
(
'favorites.models',
(u'Favorite',)
),
(
'ragendja.auth.custom_models',
(u'User',),
),
)


あと注意点がもう一つ。ローカル開発サーバーで django.contrib.csrf.middleware.CsrfMiddleware を使っているとバックアップがうまく行きませんでした。なので私はローカル開発サーバーでだけ CsrfMiddleware をオフにしています。

やった!これで Gaebar が使用できます。使う前に、Gaebar がどんな風に動くか説明しておきますね。

Gaebar は datastore のデータを Python code としてバックアップします。その Python code を実行する事でリストアします。バックアップは長くかかりますが、GAE はそれを許さないので、Gaebar は バックアップとリストアのプロセスを細切れにして ajax を使って繰り返し呼び出します。

デフォルトでは Gaebar は 5 つの Entity をバックアップし Python code に変換し、1MB の制限を回避するために 300KB くらいのコード断片として datastore に保存します。バックアップの過程では、このすぐ後に、コード断片をローカルサーバー側にダウンロードして保存する事になります。

もしあなたがもっと高い Quota を持っているなら、views.py をいじる事でこれらのデフォルト値を変えられます。

Gaebar を使う時は、十分にテストしてから使ってくださいね。万が一データが消失しても、私や Aral は責任を持てませんので。

使い方

A. リモートバックアップを取るには

1. Gaebar をあなたのアプリケーションと一緒に GAE にデプロイします

注意: デプロイする時に、gaebar/backups フォルダ内のバックアップデータも一緒にアップロードされる事を頭にいれておいてください。不必要ならバックアップデータは別の場所に移動した状態でデプロイすると良いでしょう。

2. 本番サーバで Gaebar にアクセスします。(例: http://myapp.appspot.com/gaebar/ )

3. Create New Backup ボタンをクリックします。

* バックアップをする時に、ローカル開発サーバーが動作している必要があります。

注意: reference error が発生した場合(存在しない Entity への ReferenceProperty)は、バックアップ時には無視されます。リストアすれば reference error は消滅します。

注意: 今までバックアップした一番大きなデータストアは 18995 行で 223 code 断片、35MB ありました。ローカル開発サーバーへのリストアは一晩中かかりました(ローカル開発サーバーの datastore は激遅なので)。もし記録を破ったら aral@aralbalkan.com に教えてあげてください :-)

注意2: バックアップ中の例外を詳しく見たい場合は、本番サーバーの settings.py で DEBUG_PROPAGATE_EXCEPTIONS = True と設定してください。


B. ローカルでリストアする

開発中に、リアル世界のデータを使ってテストするために、ローカルの開発サーバーにデータをリストアできます。

1. ローカル開発サーバの Gaebar ページに行きます。(例: http://localhost:8000/gaebar/ )
2. リストからリストアしたいバックアップデータを選びます。
3. 終わるまで待ちます。

tips: あらかじめ /var/tmp/ にある datastore の history ファイルのバックアップを取った方が良いです。

ローカルのデータストアは非常に遅いので、覚悟してください。

C. ステージング環境にリストアする

GAE にはバージョニング機能がありますが、datastore は別バージョン間でも共通です。ですからステージング用の別アプリを使用するのは良い考えでしょう。

本番とは別のアプリケーションスロットを用意して、そこにリストアしたいバックアップデータと一緒に Gaebar をデプロイします。後はローカル開発サーバーでのリストアと同じです。

D. 本番環境にリストアする

本番環境でトラブルがあった場合などは、バックアップからリストアできます。データストアのデータは上書きされるので注意してください。


実際に App Gallery でもバックアップ・リストアしてみましたが、完璧に動いています。Gaebar はかなり便利なのでみなさんも使ってみてはいかがでしょうか。

February 11, 2009

Mercurial deps extension

Mercurial で svn:externals みたいな事できないのか #mercurial irc で聞いてみたところ、deps extensionを使うと良いらしい。

Mercurial に同梱されてないので別途このへんから落として来て使う。

設定


deps.py を適当なところに置いて、hgrc に下記のように設定する。
[extensions]
hgext.deps =
# 又は deps.py を hgext dir 以外に置いたなら下記のようにする
# deps = /path/to/deps.py


ロケーション設定


依存関係のロケーションを設定する。これも hgrc に書く。ここで紹介する例は libfoo が外部 Mercurial リポジトリにあって、libbar は外部の CVS で管理されている場合。

[deps]
aliases = libfoo, libbar
alias.libfoo = /path/to/libfoo
alias.libbar = :pserver:anonymous@cvs.server.org:/sources/bar
alias.libbar.command = cvs -z3 -d$source co -r $rev -d $dest bar


実際の依存関係を設定


これはコマンドで行う。
$ hg deps -a libfoo -r 3a9b061bada1 -d lib/foo 0.9.1
$ hg deps -a libbar -r v0r7_8 -d lib/bar 0.9.1

$ hg deps -a libfoo -r f24139319bdb -d lib/foo 1.0
$ hg deps -a libbar -r v0r8_0 -d lib/bar 1.0


書式はこう

hg deps -a <ロケーション設定のalias名> -r <外部のリビジョン> -d  <本体のバージョン>


本体のバージョンに応じて外部リポジトリのリビジョンを変えたりできるわけか。

で、これをすると下記のような .hgdeps というファイルが出来る。これも hg add するのが良いみたい。
[1.0]
f24139319bdb libfoo lib/foo
v0r8_0 libbar lib/bar

[0.9.1]
3a9b061bada1 libfoo lib/foo
v0r7_8 libbar lib/bar


依存関係を持ってくる



下記のようにして clone するくらいしか機能は無いみたい。
$ hg depsclone 1.0


この例では、バージョン 1.0 用の外部依存関係を取ってくる事ができる。

まあすごく便利っていうわけでも無いし、使いたい人も限られてるだろうけど、Mercurial で svn:externals みたいな事がしたい人は試してみてください。

January 31, 2009

Google Apps に Group 機能が来ました

いつの間にか Google Apps の有料版にグループ機能が来ました。もしかするとコントロールパネルを拡張版にしないと出てこないかもしれません。おそらく今までの maillist 機能を拡張した機能で、ざっと触ってみたところ


  1. いくらでも入れ子に出来る

  2. メンバーのロールが Owner と Member の二種類ある

  3. ドメイン外のユーザを Owner としても Member としても追加できる

  4. このグループへの送信者を下記のグループ単位で制限できる


    • Owners
    • Members
    • ドメイン内のユーザ
    • Any mail address

  5. どうやら Google Document や Google Sites の invite の時に使えそう


今までは Google Apps を導入するお客さんには別立てでメーリングリストのサーバ(mailmanとか)を勧める事が多かったけど、これからはあまり必要なくなるかもしれませんね。

Python旅館第一回

最近やる気が無かったのですが Python旅館に来たら嘘のようにやる気が復活しました。

本当は原稿を書く予定だったんですが、隣に座った tokibitoIE6 の怖いコレ について教えてくれました。

おお!こないだ公開した My Trivia にもこのセキュリティホールあるし。早速直さないと。この OpenSocial アプリ ではユーザ入力の Trivia をサーバ側から json で返して、OpenSocial Canvas 側で表示しているのですが、とりあえず表示の時に document.createTextNode() してるから大丈夫だろうと思っていました。

しかしこういう時は < や > を下記のように処理してあげないといけないという事ですね。(この文脈では obj にユーザ入力文字が入ってます)


obj = obj.replace('<', r'\u003C')
obj = obj.replace('>', r'\u003E')


しかしこんな簡単な修正に 1h くらいかかってしまって情けない...

January 6, 2009

Blogger で Syntax Highlighting

明けましておめでとうございます。今年はすっかり寝正月でした。

突然ですが Blogger で手軽に Syntax Highlighting を使えたので書いてみます。
テンプレートのヘッダに下記を追加して

<link href='http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.css' rel='stylesheet' type='text/css'/>
<script src='http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js' type='text/javascript'/>


body の onload ハンドラを設定します。

<body onload='prettyPrint()'>


実際のコード部には class 指定した pre タグを使います。

<pre class="prettyprint">