Unix Domain SocketでXML-RPC通信

プロセス管理ツールの supervisor のソースコードを読んでいると、サーバー(supervisord)とクライアント(supervisorctl)が XML-RPC を INET ドメインソケットではなく UNIX ドメインソケットで通信していることに気づいた。
ソースコードを読んで Python での実装を確認してみた。

supervisor 起動後の状態

supervisord デーモン起動後 Unix ドメインソケットでサーバーが LISTEN されていることを確認。

$ netstat -l --protocol=unix
Active UNIX domain sockets (only servers)
Proto RefCnt Flags       Type       State         I-Node   Path
unix  2      [ ACC ]     STREAM     LISTENING     5828     @/com/ubuntu/upstart
unix  2      [ ACC ]     STREAM     LISTENING     7020     /var/run/dbus/system_bus_socket
unix  2      [ ACC ]     SEQPACKET  LISTENING     5957     @/org/kernel/udev/udevd
unix  2      [ ACC ]     STREAM     LISTENING     37510    /tmp/supervisor.sock.14587

クライアントプログラム

クライアントの XML-RPC over UNIX ドメインソケットは supervisor/xmlrpc.py で実装されている。そこから supervisor モジュールへの依存など不要なものを取っ払ったのが以下。

# Based On supervisor/xmlrpc.py
import httplib
import socket
import xmlrpclib

class UnixStreamHTTPConnection(httplib.HTTPConnection):
    def connect(self):
        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        self.sock.connect(self.socketfile)

class SupervisorTransport(xmlrpclib.Transport):
    def __init__(self, username=None, password=None, serverurl=None):
        conn = UnixStreamHTTPConnection('localhost')
        conn.socketfile = serverurl
        self.connection = conn
        self.headers = {
            "User-Agent"   : 'Dummy',
            "Content-Type" : "text/xml",
            "Accept"       : "text/xml"
        }

    def request(self, host, handler, request_body, verbose=0):
        self.headers["Content-Length"] = str(len(request_body))
        self.connection.request('POST', handler, request_body, self.headers)
        r = self.connection.getresponse()
        data = r.read()
        return data

def test_call_transport_directly():
    server = SupervisorTransport('/tmp/supervisor.sock')
    handler = '/RPC2'
    data = xmlrpclib.dumps((), 'supervisor.getAPIVersion')
    print server.request(handler, data)

def test():
    proxy = SupervisorTransport(serverurl='/tmp/supervisor.sock')
    server = xmlrpclib.Server('http://127.0.0.1', transport=proxy)
    print server.supervisor.getState()

if __name__ == '__main__':
    test()
    test_call_transport_directly()

XML-RPC サーバーを指定はするが、実際の通信は Transport を介して UNIX ドメインソケットに接続し、データのリクエスト/レスポンスも同ソケット経由で行うようにする。
あとは Unix ドメインソケットのインターフェースを HTTP に合わせてやればOK。

xmlrpclib.Transport の使い方の勉強になる。

サーバプログラム
サーバも UNIX ドメインソケットに対応しようとすると、次の Twisted-Python のメーリングリストにもあるように、ソケットファイルも URL スキームもパスを スラッシュ(/)で区切っているため、かなりグダグダな実装になる。

[Twisted-Python] XML-RPC over Unix Sockets?
http://twistedmatrix.com/pipermail/twisted-python/2009-July/019915.html

自分の能力ではコンパクトに実装できないので、実装は保留。

Leave a comment

  • Design a site like this with WordPress.com
    Get started