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はとにかくドキュメントに書いてない仕様が多すぎです><