ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

evernote-sdk-perl で Thrift::TException が発生する原因

Evernote Cloud API を使用するためには Evernote が提供するライブラリを使用する必要があります。 (Evernote Cloud API の詳細については 公式ドキュメント をご覧ください。) Perl 用のライブラリは evernote-sdk-perl です。 内部的には Thrift が使われているものの、それなりにラッピングされているので、Thrift であることをほとんど意識せずに使うことができます。

それはいいのですが、HTTP 通信周りでエラーが発生した場合には原因がなんであれ Thrift::TException が送出されてしまうため原因の特定が困難である、という問題があります。 今日もそれではまってしまったので、Thrift::TException が発生する原因としてよく遭遇しそうなものを 2 つあげておきます。

その 1 : LWP::Protocol::https モジュールがインストールされていない

Thrift モジュールの内部では LWP::UserAgent モジュールが使われています。 もし、LWP::UserAgent モジュールがインストールされていない状態で evernote-sdk-perl ライブラリを使用すると、「LWP::UserAgent がインストールされていない」 というようなエラーが出るので、LWP::UserAgetnt をインストールすればよいということがわかります。

で、cpanm などで LWP::UserAgent モジュールをインストールすると思うのですが、そのとき普通は LWP::Protocol::https モジュールはインストールされません。 しかし、Evernote との通信には HTTPS が使われるので、LWP::Protocol::https モジュールも必要となります。

LWP::Protocol::https モジュールがインストールされていないと、以下のようなオブジェクトが例外として送出されます。 わかりにくいですね。

bless( {
    'code' => 0,
    'message' => 'Missing version identifier'
}, 'Thrift::TException' )

その 2 : タイムアウトの発生

EDAMUserStore::UserStore オブジェクトを得るためには以下のようなコードを書くと思います。

my $evernote_host = 'sandbox.evernote.com';
my $user_store_url = 'https://' . $evernote_host . '/edam/user';

my $user_store_client = Thrift::HttpClient->new( $user_store_url );
my $user_store_prot = Thrift::BinaryProtocol->new( $user_store_client );
my $user_store = EDAMUserStore::UserStoreClient->new( $user_store_prot, $user_store_prot );

このとき、Thrift 内部で行われる HTTP 通信のタイムアウト時間は、デフォルトで 100 ms となっています。 (Thrift::HttpClient の $self->{sendTimeout} の値。 Thrift::HttpClient#flush メソッド内で LWP::UserAgent->new に渡される。) 100 ms というタイムアウト時間だとかなり短いですので、環境によってはタイムアウトばかりしてしまいます。 また、タイムアウトするときもあればタイムアウトしないときもある、というような現象も起こりえます。

タイムアウト時にも、上で書いたのと同じ Thrift::TException 例外が送出されます。 これも原因を見つけるのが困難なエラーのひとつだと思います。

このエラーを避けるためには、以下のようにそれなりに長いタイムアウト時間を設定すれば良いでしょう。 setSendTimeout で設定されたタイムアウト時間は、上で書いたように LWP::UserAgent->new に渡されて使用されます。 setRecvTimeout で設定されたタイムアウト時間は、現在のところ使用されている様子はありませんが、念のため設定しておくと良いかと思います。

my $evernote_host = 'sandbox.evernote.com';
my $user_store_url = 'https://' . $evernote_host . '/edam/user';

my $user_store_client = Thrift::HttpClient->new( $user_store_url );
# default timeout value may be too short
$user_store_client->setSendTimeout( 10000 );
$user_store_client->setRecvTimeout( 10000 );
my $user_store_prot = Thrift::BinaryProtocol->new( $user_store_client );
my $user_store = EDAMUserStore::UserStoreClient->new( $user_store_prot, $user_store_prot );

その他の原因

基本的にはネットワーク周りに問題がある場合に、Thrift::TException が発生するのかなーと思います。 もうちょっとわかりやすい例外が送出されればうれしいのですけどねぇ。