Source code for cothread.coserver

# This file is part of the Diamond cothread library.
#
# Copyright (C) 2014 Michael Davidsaver, Brookhaven National Laboratory
#
# The Diamond cothread library is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the License,
# or (at your option) any later version.
#
# The Diamond cothread library is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#

"""
cothread friendly versions of the socket servers
from the SocketServer and BaseHTTPServer modules
"""

import sys

from socketserver import BaseServer, TCPServer, UDPServer, ThreadingMixIn
from http.server import HTTPServer, SimpleHTTPRequestHandler, test as _test

from . import cothread
from . import cosocket
from . import coselect

__all__ = [
    'BaseServer',
    'TCPServer',
    'UDPServer',
    'HTTPServer',
    'CoThreadingMixIn',
    'CoThreadingTCPServer',
    'CoThreadingUDPServer',
    'CoThreadingHTTPServer',
]


# We must patch out use of the socket, threading, and select modules
def _patch(cls):
    def wrap(fun):
        fun.__doc__ = getattr(cls, fun.__name__).__doc__
        return fun

    class WrappedServer(cls):
        __doc__ = cls.__doc__

        @wrap
        def __init__(self, *args, **kws):
            if hasattr(cls, 'address_family'): # All except BaseServer
                baact = kws.get('bind_and_activate', True)
                kws['bind_and_activate'] = False

            cls.__init__(self, *args, **kws)

            self.__shut = cosocket.socketpair()

            if hasattr(cls, 'address_family'):
                self.socket = cosocket.cosocket(_sock = self.socket)
                if baact:
                    self.server_bind()
                    self.server_activate()

        @wrap
        def serve_forever(self, poll_interval=0.5):
            while True:
                A, B = self.fileno(), self.__shut[1].fileno()
                for S, E in coselect.poll_list(
                        [(A, coselect.POLLIN), (B, coselect.POLLIN)]):
                    if S == B:
                        self.__shut[1].recv(100)
                        return
                    elif S == A:
                        self._handle_request_noblock()

        @wrap
        def shutdown(self):
            self.__shut[0].send(b'\0')

        @wrap
        def handle_request(self):
            L = coselect.poll_list(
                [(self, coselect.POLLIN)],
                self.timeout or self.socket.gettimeout())
            if not L:
                self.handle_timeout()
            else:
                self._handle_request_noblock()

        @wrap
        def server_close(self):
            cls.server_close(self)
            self.__shut[0].close()
            self.__shut[1].close()

    WrappedServer.__name__ = cls.__name__
    return WrappedServer


BaseServer = _patch(BaseServer)
TCPServer = _patch(TCPServer)
UDPServer = _patch(UDPServer)
HTTPServer = _patch(HTTPServer)


[docs] class CoThreadingMixIn(ThreadingMixIn): def process_request(self, request, client_address): cothread.Spawn(self.process_request_thread, request, client_address)
[docs] class CoThreadingUDPServer(CoThreadingMixIn, UDPServer): pass
[docs] class CoThreadingTCPServer(CoThreadingMixIn, TCPServer): pass
[docs] class CoThreadingHTTPServer(CoThreadingMixIn, HTTPServer): pass
def test(HandlerClass=SimpleHTTPRequestHandler, ServerClass=CoThreadingHTTPServer): _test(HandlerClass, ServerClass) if __name__ == '__main__': test()