JSON-RPC の複数呼び出し

4 件のメッセージ BitcoinTalk lachesis, サトシ・ナカモト 2010年7月22日 — 2010年7月24日
lachesis 2010年7月22日 02:09 UTC 原文 ·

JSON-RPC パスワードスレッドでサトシがほのめかした、Bitcoin の JSON-RPC におけるいわゆる「Multiple Invocation」サポートに気づいた。

大量のアドレスへの支払いを 1分に 2回ポーリングするサイトを運営しているので、興味を引かれた。まず、これは JSON-RPC 2.0 の「Batch」サポートとは違う。Batch ではリクエストを配列で送信し、レスポンスも同様に受信する:

request = [
        {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
        {"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
        {"jsonrpc": "2.0", "method": "get_data", "id": "9"} 
    ]
response = [
        {"jsonrpc": "2.0", "result": 7, "id": "1"},
        {"jsonrpc": "2.0", "result": 19, "id": "2"},
        {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
    ]

代わりに別の方式で、Python でレスポンスをパースする方法が分からない。Bitcoin RPC サーバーへの telnet セッションの画面キャプチャを示す:

$ telnet localhost 8332
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
POST / HTTP/1.1
Content-Type: text/plain
Content-Length: 97

{"params":[],"id":1,"method":"getconnectioncount"}
{"params":[],"id":2,"method":"getdifficulty"}
HTTP/1.1 200 OK
Connection: close
Content-Length: 33
Content-Type: application/json
Date: Sat, 08 Jul 2006 12:04:08 GMT
Server: json-rpc/1.0

{"result":8,"error":null,"id":1}
HTTP/1.1 200 OK
Connection: close
Content-Length: 49
Content-Type: application/json
Date: Sat, 08 Jul 2006 12:04:08 GMT
Server: json-rpc/1.0

{"result":181.5432893640505,"error":null,"id":2}
Connection closed by foreign host.
見ての通り、サーバーは(予想されるような)2行を連結した1つのレスポンスではなく、2つの完全なHTTP 200レスポンスを返す。

Python で半自動的にこれをパースする方法が分からない。urllib2 も httplib も最初のレスポンスの後に戻ってきて、2 つ目を捨ててしまう。

この問題に遭遇した人はいるだろうか?この奇妙なマルチリクエスト動作を処理できる Python ライブラリを知っている人は?

lachesis 2010年7月22日 03:18 UTC 原文 ·

さて、前回の投稿から 1時間も経っていないのは分かっているが、この問題を自分で解決することにした。

もっと良い解決策があれば教えてほしいが、これが私のバージョン 0.1 の試みだ: http://www.alloscomp.com/bitcoin/btcjsonrpc.pys

気に入ったか、嫌いか、明らかな欠陥があるか教えてほしい。ソースに導入ドキュメントがあるが、テストスイートのコードを示す:

    from btcjsonrpc import Service
    s = Service()
    print 'preparing getbalance; id:',s.getbalance() # Each call returns its ID so you can find it later in the results
    print 'preparing getdifficulty; id:',s.getdifficulty() 
    print 'preparing listreceivedbyaddress; id:',s.listreceivedbyaddress(10000) # Call with a parameter
    print 'preparing getbalance; id:',s.getbalance(id='getbalance 2') # You can also specify your own ID
    print '\nexecuting call\n\nresults:'
    results = s() # Get the results by calling the Service object
    for id,value in results.iteritems():
        print id,value
    # If you'd prefer to work directly with the JSON responses instead of a dict of IDs, then access the list Service.responses.
    print '\njson responses'
    print s.responses

出力は以下の通り(docstr を含む):

$ ./btcjsonrpc.py 
 Socket-based, Bitcoin-compatible JSON-RPC v1.0 client.
 By: Eric Swanson (http://www.alloscomp.com/bitcoin)
 Version 0.1, July 21, 2010
Don't use this for one-off request->response pairs. Use something like the reference python-jsonrpc library, or urllib2 + json. This client is hackish, but it works for me (and has sped up my JSON-RPC accesses tremendously).

For details of WHY exactly I felt the need to redo python-jsonrpc using a raw socket, check out the follow forum post: topic 528

Usage is fairly straightforward, and a code sample can be found below the library code (in the if name==‘main’ clause).

preparing getbalance; id: jss-1 preparing getdifficulty; id: jss-2 preparing listreceivedbyaddress; id: jss-3 preparing getbalance; id: getbalance 2

executing call

results: jss-2 181.543289364 jss-3 ] getbalance 2 2345.94 jss-1 2345.94

json responses [{u’id’: u’jss-2’, u’result’: 181.54328936405051, u’error’: None}, {u’id’: u’jss-3’, u’result’: ], u’error’: None}, {u’id’: u’getbalance 2’, u’result’: 2345.9400000000001, u’error’: None}, {u’id’: u’jss-1’, u’result’: 2345.9400000000001, u’error’: None}]

ヘッダーが繰り返されるのは明らかにバグだ。

1.0 仕様に従おうとしていた: http://json-rpc.org/wiki/specification 複数呼び出しが規定されていた。

このようなことを意味していると思うが、確信はない:

Post:

{"method": "postMessage", "params": ["Hello all!"], "id": 99}
{"method": "postMessage", "params": ["I have a question:"], "id": 101}

Reply:

{"result": 1, "error": null, "id": 99}
{"result": 1, "error": null, "id": 101}

エラー応答に HTTP ステータス 500 を返すべきだとどこかで見た気がするが、思い出せない。複数のレスポンスを含み、そのうちの 1 つがエラーの場合、全体のステータスが 500 になるのだろうか。おそらくそうだろう。常に 200 を返すべきかもしれない。500 が問題を引き起こしているような指摘があったと思う。

これはおそらく 0.3.3 の後に修正される。それまでは単一呼び出しを使用してくれ。JSON-RPC パッケージで複数呼び出しをサポートしているものがあるかどうか疑問だが、おそらくないだろう。

修正を試みる前に、複数呼び出しがどのように機能すべきか(そもそも機能すべきなのか)、またエラーレスポンスに HTTP ステータス 500 を返すのが正しいかどうかを、もう少し明確にできると良いのだが。

lachesis 2010年7月24日 02:36 UTC 原文 ·

そうだな、1.0 の仕様は少し変だ。JSON-RPC 2.0 ではきちんと定められている。リクエストのリストに対してレスポンスのリストが返る。私の知る限り、JSON-RPC クライアントライブラリのどれもエラーコードを扱っていない。どれも JSON-RPC レスポンスの error フィールドを見るだけだ。最低でも、リクエストのいずれかが成功した場合には 200 を返すようにする。