ラベル Python 2.5.2 の投稿を表示しています。 すべての投稿を表示
ラベル Python 2.5.2 の投稿を表示しています。 すべての投稿を表示

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月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ヘッダがめちゃくちゃだ!!ここを直せば動くかも。

2008年10月16日木曜日

Pythonで変数の型をチェックする方法(Javaでいうinstanceofが使いたい)

  • リスト、辞書、タプルのようなイテレータが使える型には__iter__属性がある
  • ただし文字列には__iter__がない
  • Javaでいうinstanceofがやりたいときは、組み込み関数isinstance(obj, type)を使う
  • issubclass(obj, type)というのもある
基本的にPythonではあまり変数の型を気にしないようなのですが、
それでもどうしても型チェックがやりたくなるときはあります。
引数が本当にリストなのかどうかチェックしたりとか、引数をfor()文でループさせたいときとか。
そういった場合にPythonではどうすればいいのか調べてみました。
以下、すべてPython2.5.2で動作確認しています。

Javaでいうinstanceofに相当するPythonの組み込み関数はisinstance(obj, type)です。このメソッドを使えば、引数がリストか、文字列かといったチェックは簡単にできます。
#listかどうか
isinstance([1,2,3], list)

#辞書かどうか
isinstance({'a':1, 'b':2}, dict)

#タプルかどうか
isinstance((1,2,3), tuple)
引数がiterableかどうか(for文でループできるかどうか)の判定はちょっと複雑で、以下のようになります。
#itarableかどうか(Stringを含む)
#実際にはgetattr(obj, '__iter__', False)よりもhasattr(obj, '__iter__')のほうがスマート
isiterable = lambda obj: isinstance(obj, basestring) or getattr(obj, '__iter__', False)
isiterable('abcde')

#iterableかどうか(Stringは含まない)
getattr([1,2,3], '__iter__', False)
#またはhasattrを使って
hasattr([1,2,3], '__iter__')
普通のiterableなオブジェクトは__iter__属性を持っているのでこれですぐに判断できます。
しかしなぜか文字列だけは__iter__属性がないのでisinstance()を併用します。

※一部http://bytes.com/forum/thread514838.htmlより転載いたしました。ありがとうございました。