TSV-RPCでお気楽ネットワークプログラミング

ID: 97
creation date: 2010/09/29 23:21
modification date: 2010/09/29 23:21
owner: mikio

古き良きタブ区切りテキストを使って簡単かつ効率的にRPCを実現してみた。単純なユースケースではXML-RPCより便利に使えるよ。

チュートリアル

ともかく実際に体験してもらうのがわかりやすいと思う。DBMライブラリKyoto Cabinetと永続キャッシュサーバKyoto Tycoonをまずはインストールしていただきたい。KCを入れてから、KTを入れる。両者とも、ソースアーカイブを展開したディレクトリに入って以下のコマンドを実効すればビルドとインストールが完了する。

$ ./configure
$ make
$ sudo make install

で、TSV-RPCサーバの典型的な実装例であるところのKyoto Tycoonサーバを起動する。端末で以下のコマンドを実行するだけでよい(終了するには端末にCtrl-Dを入力)。

$ ktserver

デフォルトの設定では、オンメモリのキャッシュデータベースが開かれて、その操作を行うためのサービスが提供される。さて、早速TSV-RPCを使ってアクセスしてみよう。別の端末から以下のコマンドを入力してみてほしい。

$ curl "http://localhost:1978/rpc/report"
count   0
db_0    count=0 size=8390416 path=*
kc_version      1.2.14 (5.6)
os      Linux
pid     13011
size    8390416
sys_mem_cached  678551552
sys_mem_free    1204854784
sys_mem_peak    77844480
sys_mem_rss     77844480
sys_mem_size    2555904
sys_mem_total   3943903232
sys_ru_stime    0.000000
sys_ru_utime    0.010000
time    28.959394
version 0.6.0 (1.1)

上記では、ローカルホストの1978番ポートで起動しているHTTPサーバに「/rpc/report」というリソースを要求している。これはサーバの統計情報のレポートを返すコマンドである。出力がTSVとして返されているのがわかるだろう。レコードを格納したり取得したり削除したりもできる。

$ curl "http://localhost:1978/rpc/set?key=japan&value=tokyo"
$ curl "http://localhost:1978/rpc/get?key=japan"
value   tokyo
$ curl "http://localhost:1978/rpc/remove?key=japan"
$ curl "http://localhost:1978/rpc/get?key=japan"
ERROR   DB: 7: no record: no record

このように、RPCサーバへの入力はURLのクエリ文字列で指定することができ、出力はTSVで受け取るというのが、TSV-RPCの基本的な仕様である。そんだけ。簡単。もうちょい説明すると、RPCの入力と出力は両方とも文字列の連想配列(C++で言う所のstd::map<std::string,std::string>)ということになっていて、TSVの各行が各レコードに相当し、1列目がキー、2列目が値として扱われるということだ。

仕様が究極的に簡単なので、curl(KTとは何の関係もない素のHTTPクライアント)さえあれば、シェルから呼び出してgrepやsedと組み合わせるだけでちょっとしたプログラムが簡単に書ける。これがXMLだったらそうはいかないよね。もちろん、RubyやPerlやPythonからHTTPライブラリを使って操作してもよい。その場合も出力の解析は「split」して連想配列にぶっこむだけなんで非常に楽だ。

任意の文字列やバイナリを扱うには

TSVはタブと改行でレコードを区切るので、レコードのキーや値にタブや改行が入っていると困る。それらを含むかもしれない任意の文字列やバイナリを扱う場合には、何らかのエンコードを施す必要がある。例として、無理やり値にヌルコードを入れてからそれを取得してみよう。また、curlに -i をつけてHTTPヘッダも表示してみる。

$ curl -i "http://localhost:1978/rpc/set?key=foo&value=%00"
HTTP/1.1 200 OK
Server: KyotoTycoon/0.6.0
Date: Wed, 29 Sep 2010 13:44:52 GMT
Content-Length: 0
Content-Type: text/tab-separated-values

$ curl -i "http://localhost:1978/rpc/get?key=foo"
HTTP/1.1 200 OK
Server: KyotoTycoon/0.6.0
Date: Wed, 29 Sep 2010 13:45:11 GMT
Content-Length: 10
Content-Type: text/tab-separated-values; colenc=U

value   %00

URLのクエリ文字列として入力パラメータを指定する際には、当然ながらURLエンコードでデータを扱う必要がある。なので、setの際にはヌルコードを「%00」としている。次にgetの出力を見ると、「%00」となっている。つまり出力もURLエンコードされたということだ。Content-Typeヘッダの値が「text/tab-separated-values; colenc=U」となっているが、これはTSV文字列の各フィールドがURLエンコードされているということを示している。

この「colenc=U」というのは俺が勝手に決めた仕様である。エンコード方式には以下のものがあり、クライアントはヘッダを見てそれに応じた処理を行うことが求められる。

  • colenc=B : Base64エンコード
  • colenc=Q : Quoted-printableエンコード
  • colenc=U : URLエンコード

実際には、Qは使われない。BかUかは、サーバ側が出力内容を判断して、エンコード後のサイズが小さくなる方式が自動的に選択される。出力内容にタブや改行やヌルコードが含まれない場合は、エンコードは処理を行わずに生データが転送され、Content-Typeのcolenc属性もつかない。

でかいデータを入力するには

URLのクエリ文字列として送信できるデータにはサーバ側の実装依存の上限があるので、1KBとかを越えるデータを送るのは止めた方がよい。そのようなでかいデータを送るには、POSTメソッドでエンティティボディとして送ることになる。その際には、出力のTSVと同様に、各フィールドを必要に応じてエンコードしたTSVを送る。例えばこんな感じ。エンコード方式にはBでもQでもUでも好きなものを選べばよい。

POST /rpc/set HTTP/1.1
Host: localhost:1978
Content-Length: 59
Content-Type: text/tab-separated-values; colenc=U

key     mikio
value   %e5%b9%b3%e6%9e%97%e5%b9%b9%e9%9b%84

時限削除(expiration)もできる

TSV-RPCの話とはちょっとずれるけども、KTはexpiration機能がついたので便利だよという宣伝。こんな風にやる。

$ curl "http://localhost:1978/rpc/set?key=abcd&value=efgh&xt=30"
$ curl "http://localhost:1978/rpc/get?key=abcd"
value   efgh
xt      1285769110

# 30秒待つ

$ curl "http://localhost:1978/rpc/get?key=abcd"
ERROR   DB: 7: no record: no record

ちゃんと消えてるね。ということで、現状で既にmemcachedのような用途で利用することができる。クライアント用の高水準APIは現状ではC++版しかないけれども、TSV-RPCはカジュアルプログラマの友達なので、高水準ライブラリなんて無くても適当にHTTPライブラリをしばけばクライアント側のプログラミングは簡単である。

まとめ

TSV-RPC、原始的だけど、便利だよね。便利なだけじゃなくて、XMLとかJSONとかよりも空間効率が高いのは明白だし、実行時の時間効率においても有利だろう。バイナリの扱いも得意だ。連想配列よりもさらに複雑なデータ構造の授受を行おうとすると、自前で何らかのシリアライズをすることになってちょっと面倒だけれども、少なくともKTではこの仕様で十分である。

KTのコアライブラリにマルチスレッドHTTPサーバのフレームワークが同梱されているのは以前の記事で述べたが、その上にTSV-RPCのフレームワークも構築して同梱している。なので、例によって50行くらいでRPCのサーバ側も記述することができる。カジュアルにRPCをゴニョりたい人はお試しあれ。

5 comments
jose : I think you confuse RPC with REST (2010/09/30 23:41)
jose : http://steve.vinoski.net/pdf/IEEE-RPC_Under_Fire.pdf (2010/09/30 23:42)
jose : One way that REST differs from RPC is that it works with a fixed set of verbs, such as the HTTP verbs GET and POST, whereas RPC promotes specialized verbs for each interface (2010/09/30 23:42)
mikio : Thanks. But, I didn't say that I used REST archtecture. I know that REST differs from RPC. (2010/10/01 03:42)
mikio : While I think REST is suitable for many services over WWW, I designed TSV-RPC for backend servers which are used behind public web applications. (2010/10/01 03:46)
riddle for guest comment authorization:
Where is the capital city of Japan? ...