2009年10月17日土曜日

PythonのEvernote APIをProxyに対応させることに成功しました

前回記事で見事大失敗したので、今回改めてThriftのTHttpClientのPython実装にProxy機能を持たせるチャレンジを行いました。結果、無事成功しました!さっそく共有させていただきたいと思います。


■PythonのEvernote APIをProxyに対応させる方法
まずはEvernote APIの中で使用されている通信クラス、thrift.transport.THttpClientを修正する必要があります。
以下のようにTHttpClient.pyを修正してください(または別名ファイルとして保存してください)。

http://gist.github.com/204501

修正しましたら、APIを呼びだす側のコードを以下のように変更してください。
# これを・・・
# import thrift.transport.THttpClient as THttpClient
# こうする
import thrift.transport.MyTHttpClient as THttpClient
import evernote.edam.userstore.UserStore as UserStore
import evernote.edam.userstore.constants as UserStoreConstants
import evernote.edam.notestore.NoteStore as NoteStore
import evernote.edam.type.ttypes as Types

##########
# 中略
##########

# プロキシを使わなくて良いときはproxy引数を無視すればよい
# userStoreHttpClient = THttpClient.THttpClient(userStoreUri)
# プロキシを使いたいときはこうする
userStoreHttpClient = THttpClient.THttpClient(userStoreUri, proxy="myproxy.example.com:8080")
userStoreProtocol = TBinaryProtocol.TBinaryProtocol(userStoreHttpClient)
userStore = UserStore.Client(userStoreProtocol)

versionOK = userStore.checkVersion(applicationClientNameString,
                                   UserStoreConstants.EDAM_VERSION_MAJOR,
                                   UserStoreConstants.EDAM_VERSION_MINOR)
これだけで通信時にプロキシを介してくれるようになりました!


■何をやっているか
そもそもThriftのTHttpClientはプロキシを使うこと自体が全く想定されていない?作りになってまして、他の言語の実装でもすべてプロキシは使えるようになっていません。たとえばC#での実装はこちら(http://issues.apache.org/jira/secure/attachment/12391517/THttpClient.cs)ですが、これもプロキシは使えません。connection.Proxy = null;ってなってます。

というわけで、勝手に自分でTHttpClientを再実装してプロキシを使えるようにしています。あんまりよい方法ではないと思いますので、本番サーバーなどでは真似しないでください><


■前回からの改良点
前回はhttplib.HTTPを使うのを辞めて、httplib.HTTPConnectionクラスを使うようにしたのですが、コレがそもそもの大失敗でした。といいますか、httplib.HTTPのソースを見たら内部でばっちりhttplib.HTTPConnectionが使われてるじゃないですかorz
self.__http = httplib.HTTP(self.host, self.port)
self.__http._conn #これでhttplib.HTTPConnectionが取得できる
そして前回最大の失敗がread()メソッドが呼ばれるたびに接続をcloseしてしまっていたところです。httplib.HTTPの実装をみてようやく気づいたのですが、ここでは接続をCloseしてはいけないんですね・・・
これらを踏まえて、今回はTHttpClientの中でhttplib.HTTPをそのまま使うようにして解決しました。

他にもhttplib.HTTPResponseは以下のようにしてファイルポインタが取得できるとか
response = conn.getresponse()
response.fp # これがファイルポインタ。httplib.HTTP.read()でも使われている
httplibはとにかくドキュメントに書いてない仕様が多すぎです><

2009年10月11日日曜日

UIViewControllerのtouchesBeganとかtouchesEndedが上手く機能しなかったと思ったら・・・

ひさっびさに普通のUIKitを使ったiPhoneアプリを作ったりし始めたらかなりの範囲を忘れてしまっていて大ハマりしてます。中でも一番困ったのがこれ。
@implementation AbesiViewController

- (void)viewDidLoad
{
[super viewDidLoad];
self.wantsFullScreenLayout = YES;
}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}

- (void)dealloc
{
[super dealloc];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesBegan");
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesMoved");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// なぜか出力されない
NSLog(@"touchesEnded");
}

@end
とまぁ、なんの変哲もないUIViewControllerにタッチを扱うためのイベントハンドラを搭載してみただけなのですが、コレがまぁ動かない動かない!

こういうときに考えられる原因は、だいたいがInterface BuilderでInteraction Enabledのチェックを入れ忘れているとか、ViewControllerとViewをつなぎ忘れているとかそんなのばっかりなので真っ先に調べてみました。が、やはり問題は見つかりません。

試しにUIViewControllerではなくUIViewにtouchesBeganを載せてみるとコレが動くんです。ああ、OS 3.1.2ぐらいから挙動が変わったのかなとか思っていたら、

http://stackoverflow.com/questions/1025574/uiviewcontroller-not-receiving-touchesbegan-message
OK, I'm a dummy. It works fine. The problem was, I didn't realize I was sending a release message to the UIViewController without having retained it elsewhere first. So that was causing the problem.
あーーー!そうだ!!!UIViewControllerをretainしてない!!!orz

retainしたら解決しました。ほんと腕がなまってる・・・

2009年10月8日木曜日

PythonのEvernote APIをプロキシに対応させようとして挫折した

Evernote APIで使用されているThriftのTHttpClientクラスには、プロキシの設定を行うためのインターフェースが用意されていません。そのため、プロキシ越しにEvernote APIの呼び出しを行うことが出来ないみたいです。
(ひょっとしたら出来るのかもしれませんが、調べた範囲ではわからず)

そこでThriftのTHttpClientクラスのPython実装をプロキシに対応させるべくチャレンジしてみました。

http://gist.github.com/204501

基本的には元々httplib.HTTPで実装されていたところをhttplib.HTTPConnectionに変えただけなのですが、ところがこれがうまくいかない。このクライアントで/edam/user/にCheckVersionリクエストを投げると500エラーが返却されてしまいます。で、500エラーを拾うと再度試行するようになっているのか、あえなく無限ループに突入><
プロキシの設定が間違っているのかと思い、試しにプロキシを外してデバッグログをはかせた結果がこちら。
send: 'POST /edam/user HTTP/1.1\r\nHost: sandbox.evernote.com:443\r\nAccept-Encoding: identity\r\nHost: sandbox.evernote.com\r\nContent-Type: application/x-thrift\r\nContent-Length: 82\r\n\r\n'
send: '\x80\x01\x00\x01\x00\x00\x00\x0ccheckVersion\x00\x00\x00\x00\x0b\x00\x01\x00\x00\x00(Python URL to Evernote/0.1; Python/2.5.2\x06\x00\x02\x00\x01\x06\x00\x03\x00\r\x00'
debug: HTTPConnection
Request-sent None
reply: 'HTTP/1.1 200 OK\r\n'
header: Server: Apache-Coyote/1.1
header: Content-Type: application/x-thrift
header: Content-Length: 29
header: Date: Wed, 07 Oct 2009 09:04:29 GMT
read start
read POST request
send: 'POST /edam/user HTTP/1.1\r\nHost: sandbox.evernote.com:443\r\nAccept-Encoding: identity\r\n\r\n'
read getresponse
debug: HTTPConnection
Request-sent None
reply: 'HTTP/1.1 500 \r\n'
header: Server: Apache-Coyote/1.1
header: ETag: W/"3401-1254794526000"
header: Last-Modified: Tue, 06 Oct 2009 02:02:06 GMT
header: Content-Type: text/html
header: Content-Length: 3401
header: Date: Wed, 07 Oct 2009 09:04:29 GMT
header: Connection: close
read start
read POST request
うーん、HTTPSの実装が間違っているのか、リクエストパラメータが間違っているのか。1回目のリクエストはうまくいくけど2回目のリクエストが失敗しがちみたいです。もう少し調査してみます。


あ!今気づいた!!
2回目のリクエスト時(readメソッド実行時)のHTTPヘッダがめちゃくちゃだ!!ここを直せば動くかも。