taken by saschaaa (under CC)
XML RPCで最近良く遊んでいます。ぱっとサーバを立ち上げて、インタラクティブモードでクライアントを起動して、すぐにメソッドを呼び出したり、すごく便利です。dRubyみたいに、クライアントとサーバの区別なく使えて、コールバックもできて、オブジェクトも渡したりできるような仕組みと比べると低レベルですが、ちょっとした通信では結構便利です。
ですが、標準のSimpleXMLRPCServerを使って分かったのは、Djangoのテストサーバの偉いところです。Ctrl+Cを押すとその場でぱっと止まってくれます。でも、自分でSimpleHTTPServer, SimpleXMLRPCServerなんかを起動すると、止まってくれません。気軽にインタラクティブモードで起動したり閉じたりするのには不便です。Djangoは別にプロセスをあげてそこで起動しているため、メインプロセス側のKeyboardInterruptをうまく捕まえて処理できているようです。では、通常のサーバではそこまでやらなければならないのか、というと、別の方法があります。下記のサイトを参考にコードを書いてみました。
それでは、作った物を公開します。
# -*- coding: utf_8 -*- """外部から停止できるXMLRPCServer""" import select import threading from SimpleXMLRPCServer import SimpleXMLRPCServer class StoppableXMLRPCServer(SimpleXMLRPCServer): def __init__(self, *args, **kwargs): SimpleXMLRPCServer.__init__(self, *args, **kwargs) self.__serving = False self.__thread = None self.__is_shut_down = threading.Event() def serve_forever(self, in_thread=False): def serve_thread(server): server.serve_forever() if in_thread: self.__thread = threading.Thread(target=serve_thread, args=[self]) self.__thread.start() else: self.__serving = True self.__is_shut_down.clear() while self.__serving: self.handle_request() self.__is_shut_down.set() def get_request(self): while True: r = select.select([self.socket], [], [], 1) if len(r[0]) > 0: return self.socket.accept() if not self.__serving: return (None, None) def verify_request(self, request, client_address): return request is not None def shutdown(self): if not self.__serving: return self.__is_shut_down.wait() self.__serving = False if self.__thread: self.__thread.join() self.__thread = None if __name__ == "__main__": def add(a, b): return a + b server = StoppableXMLRPCServer(("localhost", 8000)) server.register_function(add) server.serve_forever()
止め方としては、2種類用意しています。
- serve_forever()で起動した後に、Ctrl+Cで止める
- 別スレッドで立ち上げておいて、オブジェクト.shutdown()をコール
起動の仕方も2種類用意しています。通常の、ブロックされてしまう、serve_forever()のコールと、serve_forever(in_thread=True)として起動する方法です。後者でサーバを立ち上げると、処理はすぐに戻ってくるようになります。内部で別スレッドを起動し、その中でサーバを立ち上げます。XML RPCサーバをWindowsのサービスにする場合など、外部から処理のスタートや停止をするような場合にはこれができた方が便利かな、と思って実装してみました。
ちなみに、別スレッドで起動というのは僕がオリジナルで入れたコードですが、それ以外の物に関しては、Python3.0のコードを見たら既に対応済みでした。shutdown()というメソッド名なども、Python3.0にあわせてあります。ということで、このコードはPython2系限定(正確には2.5でしかテストしてない)です。
次はクライアント側のServerProxyの方かな。相手がいないと固まってしまう問題がなんとかできたらいいな、と思ってます。これからコード読もう。