Example #1
0
 def testServer_select(self):
     daemon=ServerCallback()
     port=SU.findProbablyUnusedPort()
     serv=SocketServer_Select()
     serv.init(daemon,"localhost",port)
     self.assertEqual("localhost:"+str(port), serv.locationStr)
     self.assertTrue(serv.sock is not None)
     conn=SU.SocketConnection(serv.sock, "ID12345")
     self.assertEqual("ID12345",conn.objectId)
     self.assertTrue(conn.sock is not None)
     conn.close()
     conn.close()
     self.assertFalse(conn.sock is None, "connections keep their socket object even if it's closed")
     serv.close()
     serv.close()
     self.assertTrue(serv.sock is None)
Example #2
0
 def __init__(self, host=None, port=0, unixsocket=None, nathost=None, natport=None, interface=DaemonObject):
     _check_hmac()  # check if hmac secret key is set
     if host is None:
         host=Pyro4.config.HOST
     if nathost is None:
         nathost=Pyro4.config.NATHOST
     if natport is None:
         natport=Pyro4.config.NATPORT or None
     if nathost and unixsocket:
         raise ValueError("cannot use nathost together with unixsocket")
     if (nathost is None) ^ (natport is None):
         raise ValueError("must provide natport with nathost")
     if Pyro4.config.SERVERTYPE=="thread":
         self.transportServer=SocketServer_Threadpool()
     elif Pyro4.config.SERVERTYPE=="multiplex":
         # choose the 'best' multiplexing implementation
         if os.name=="java":
             raise NotImplementedError("select or poll-based server is not supported for jython, use thread server instead")
         import select
         if hasattr(select,"poll"):
             self.transportServer=SocketServer_Poll()
         else:
             self.transportServer=SocketServer_Select()
     else:
         raise errors.PyroError("invalid server type '%s'" % Pyro4.config.SERVERTYPE)
     self.transportServer.init(self, host, port, unixsocket)
     #: The location (str of the form ``host:portnumber``) on which the Daemon is listening
     self.locationStr=self.transportServer.locationStr
     log.debug("created daemon on %s", self.locationStr)
     natport_for_loc = natport
     if natport==0:
         # expose internal port number as NAT port as well. (don't use port because it could be 0 and will be chosen by the OS)
         natport_for_loc = int(self.locationStr.split(":")[1])
     #: The NAT-location (str of the form ``nathost:natportnumber``) on which the Daemon is exposed for use with NAT-routing
     self.natLocationStr = "%s:%d" % (nathost, natport_for_loc) if nathost else None
     if self.natLocationStr:
         log.debug("NAT address is %s", self.natLocationStr)
     self.serializer=util.Serializer()
     pyroObject=interface(self)
     pyroObject._pyroId=constants.DAEMON_NAME
     #: Dictionary from Pyro object id to the actual Pyro object registered by this id
     self.objectsById={pyroObject._pyroId: pyroObject}
     self.__mustshutdown=threadutil.Event()
     self.__loopstopped=threadutil.Event()
     self.__loopstopped.set()
     self._uriToFuture = {}
Example #3
0
 def __init__(self, host=None, port=0, unixsocket=None, nathost=None, natport=None):
     _check_hmac()  # check if hmac secret key is set
     if host is None:
         host=Pyro4.config.HOST
     if nathost is None:
         nathost=Pyro4.config.NATHOST
     if natport is None:
         natport=Pyro4.config.NATPORT or None
     if nathost and unixsocket:
         raise ValueError("cannot use nathost together with unixsocket")
     if (nathost is None) ^ (natport is None):
         raise ValueError("must provide natport with nathost")
     if Pyro4.config.SERVERTYPE=="thread":
         self.transportServer=SocketServer_Threadpool()
     elif Pyro4.config.SERVERTYPE=="multiplex":
         # choose the 'best' multiplexing implementation
         if os.name=="java":
             self.transportServer = SocketServer_Select()    # poll doesn't work as given in jython ('socket must be in nonblocking mode')
         else:
             self.transportServer = SocketServer_Poll() if socketutil.hasPoll else SocketServer_Select()
     else:
         raise errors.PyroError("invalid server type '%s'" % Pyro4.config.SERVERTYPE)
     self.transportServer.init(self, host, port, unixsocket)
     #: The location (str of the form ``host:portnumber``) on which the Daemon is listening
     self.locationStr=self.transportServer.locationStr
     log.debug("created daemon on %s", self.locationStr)
     natport_for_loc = natport
     if natport==0:
         # expose internal port number as NAT port as well. (don't use port because it could be 0 and will be chosen by the OS)
         natport_for_loc = int(self.locationStr.split(":")[1])
     #: The NAT-location (str of the form ``nathost:natportnumber``) on which the Daemon is exposed for use with NAT-routing
     self.natLocationStr = "%s:%d" % (nathost, natport_for_loc) if nathost else None
     if self.natLocationStr:
         log.debug("NAT address is %s", self.natLocationStr)
     pyroObject=DaemonObject(self)
     pyroObject._pyroId=constants.DAEMON_NAME
     #: Dictionary from Pyro object id to the actual Pyro object registered by this id
     self.objectsById={pyroObject._pyroId: pyroObject}
     self.__mustshutdown=threadutil.Event()
     self.__loopstopped=threadutil.Event()
     self.__loopstopped.set()
     # assert that the configured serializers are available, and remember their ids:
     self.__serializer_ids = set([util.get_serializer(ser_name).serializer_id for ser_name in Pyro4.config.SERIALIZERS_ACCEPTED])
     log.debug("accepted serializers: %s" % Pyro4.config.SERIALIZERS_ACCEPTED)
Example #4
0
 def testServer_select(self):
     daemon = ServerCallback()
     port = SU.findProbablyUnusedPort()
     serv = SocketServer_Select()
     serv.init(daemon, "localhost", port)
     self.assertEqual("localhost:" + str(port), serv.locationStr)
     self.assertIsNotNone(serv.sock)
     conn = SU.SocketConnection(serv.sock, "ID12345")
     self.assertEqual("ID12345", conn.objectId)
     self.assertIsNotNone(conn.sock)
     conn.close()
     conn.close()
     self.assertIsNotNone(
         conn.sock,
         "connections keep their socket object even if it's closed")
     serv.close()
     serv.close()
     self.assertIsNone(serv.sock)
Example #5
0
class Daemon(object):
    """
    Pyro daemon. Contains server side logic and dispatches incoming remote method calls
    to the appropriate objects.
    """
    serializers=dict() # dict of type -> serializer
    
    def __init__(self, host=None, port=0, unixsocket=None, nathost=None, natport=None, interface=DaemonObject):
        _check_hmac()  # check if hmac secret key is set
        if host is None:
            host=Pyro4.config.HOST
        if nathost is None:
            nathost=Pyro4.config.NATHOST
        if natport is None:
            natport=Pyro4.config.NATPORT or None
        if nathost and unixsocket:
            raise ValueError("cannot use nathost together with unixsocket")
        if (nathost is None) ^ (natport is None):
            raise ValueError("must provide natport with nathost")
        if Pyro4.config.SERVERTYPE=="thread":
            self.transportServer=SocketServer_Threadpool()
        elif Pyro4.config.SERVERTYPE=="multiplex":
            # choose the 'best' multiplexing implementation
            if os.name=="java":
                raise NotImplementedError("select or poll-based server is not supported for jython, use thread server instead")
            import select
            if hasattr(select,"poll"):
                self.transportServer=SocketServer_Poll()
            else:
                self.transportServer=SocketServer_Select()
        else:
            raise errors.PyroError("invalid server type '%s'" % Pyro4.config.SERVERTYPE)
        self.transportServer.init(self, host, port, unixsocket)
        #: The location (str of the form ``host:portnumber``) on which the Daemon is listening
        self.locationStr=self.transportServer.locationStr
        log.debug("created daemon on %s", self.locationStr)
        natport_for_loc = natport
        if natport==0:
            # expose internal port number as NAT port as well. (don't use port because it could be 0 and will be chosen by the OS)
            natport_for_loc = int(self.locationStr.split(":")[1])
        #: The NAT-location (str of the form ``nathost:natportnumber``) on which the Daemon is exposed for use with NAT-routing
        self.natLocationStr = "%s:%d" % (nathost, natport_for_loc) if nathost else None
        if self.natLocationStr:
            log.debug("NAT address is %s", self.natLocationStr)
        self.serializer=util.Serializer()
        pyroObject=interface(self)
        pyroObject._pyroId=constants.DAEMON_NAME
        #: Dictionary from Pyro object id to the actual Pyro object registered by this id
        self.objectsById={pyroObject._pyroId: pyroObject}
        self.__mustshutdown=threadutil.Event()
        self.__loopstopped=threadutil.Event()
        self.__loopstopped.set()
        self._uriToFuture = {}

    @property
    def sock(self):
        return self.transportServer.sock

    @property
    def sockets(self):
        return self.transportServer.sockets

    @staticmethod
    def serveSimple(objects, host=None, port=0, daemon=None, ns=True, verbose=True):
        """
        Very basic method to fire up a daemon (or supply one yourself).
        objects is a dict containing objects to register as keys, and
        their names (or None) as values. If ns is true they will be registered
        in the naming server as well, otherwise they just stay local.
        """
        if not daemon:
            daemon=Daemon(host, port)
        with daemon:
            if ns:
                ns=Pyro4.naming.locateNS()
            for obj, name in objects.items():
                if ns:
                    localname=None   # name is used for the name server
                else:
                    localname=name   # no name server, use name in daemon
                uri=daemon.register(obj, localname)
                if verbose:
                    print("Object {0}:\n    uri = {1}".format(repr(obj), uri))
                if name and ns:
                    ns.register(name, uri)
                    if verbose:
                        print("    name = {0}".format(name))
            if verbose:
                print("Pyro daemon running.")
            daemon.requestLoop()

    def requestLoop(self, loopCondition=lambda: True):
        """
        Goes in a loop to service incoming requests, until someone breaks this
        or calls shutdown from another thread.
        """
        self.__mustshutdown.clear()
        log.info("daemon %s entering requestloop", self.locationStr)
        try:
            self.__loopstopped.clear()
            condition=lambda: not self.__mustshutdown.isSet() and loopCondition()
            self.transportServer.loop(loopCondition=condition)
        finally:
            self.__loopstopped.set()
        log.debug("daemon exits requestloop")

    def events(self, eventsockets):
        """for use in an external event loop: handle any requests that are pending for this daemon"""
        return self.transportServer.events(eventsockets)

    def shutdown(self):
        """Cleanly terminate a daemon that is running in the requestloop. It must be running
        in a different thread, or this method will deadlock."""
        log.debug("daemon shutting down")
        self.__mustshutdown.set()
        self.transportServer.wakeup()
        time.sleep(0.05)
        self.close()
        self.__loopstopped.wait()
        log.info("daemon %s shut down", self.locationStr)

    def _handshake(self, conn):
        """Perform connection handshake with new clients"""
        # For now, client is not sending anything. Just respond with a CONNECT_OK.
        # We need a minimal amount of data or the socket will remain blocked
        # on some systems... (messages smaller than 40 bytes)
        # Return True for successful handshake, False if something was wrong.
        data="ok"
        if sys.version_info>=(3,0):
            data=bytes(data,"utf-8")
        msg=MessageFactory.createMessage(MessageFactory.MSG_CONNECTOK, data, 0, 1)
        conn.send(msg)
        return True

    def handleRequest(self, conn):
        """
        Handle incoming Pyro request. Catches any exception that may occur and
        wraps it in a reply to the calling side, as to not make this server side loop
        terminate due to exceptions caused by remote invocations.
        """
        flags=0
        seq=0
        wasBatched=False
        isCallback=False
        client_future = None
        try:
            msgType, flags, seq, data = MessageFactory.getMessage(conn, MessageFactory.MSG_INVOKE)
            objId, method, vargs, kwargs=self.serializer.deserialize(
                                           data, compressed=flags & MessageFactory.FLAGS_COMPRESSED)
            del data  # invite GC to collect the object, don't wait for out-of-scope
            obj=self.objectsById.get(objId)
            
            if flags & MessageFactory.FLAGS_ASYNC:
                client_future = vargs[0]
                client_future._pyroOneway.update(["set_cancelled", "set_result", "set_exception", "set_progress"])
                vargs = vargs[1:]
            elif flags & MessageFactory.FLAGS_ASYNC_CANCEL:
                client_future_uri = vargs[0]
            
            if obj is not None:
                if kwargs and sys.version_info<(2, 6, 5) and os.name!="java":
                    # Python before 2.6.5 doesn't accept unicode keyword arguments
                    kwargs = dict((str(k), kwargs[k]) for k in kwargs)
                if flags & MessageFactory.FLAGS_BATCH:
                    # batched method calls, loop over them all and collect all results
                    data=[]
                    for method,vargs,kwargs in vargs:
                        method=util.resolveDottedAttribute(obj, method, Pyro4.config.DOTTEDNAMES)
                        try:
                            result=method(*vargs, **kwargs)   # this is the actual method call to the Pyro object
                        except Exception:
                            xt,xv=sys.exc_info()[0:2]
                            log.debug("Exception occurred while handling batched request: %s", xv)
                            xv._pyroTraceback=util.formatTraceback(detailed=Pyro4.config.DETAILED_TRACEBACK)
                            if sys.platform=="cli":
                                util.fixIronPythonExceptionForPickle(xv, True)  # piggyback attributes
                            data.append(futures._ExceptionWrapper(xv))
                            break   # stop processing the rest of the batch
                        else:
                            data.append(result)
                    wasBatched=True
                elif flags & MessageFactory.FLAGS_ASYNC_CANCEL:
                    data=self._cancelFuture(client_future_uri)
                else:
                    # normal single method call
                    method=util.resolveDottedAttribute(obj, method, Pyro4.config.DOTTEDNAMES)
                    if flags & MessageFactory.FLAGS_ONEWAY and Pyro4.config.ONEWAY_THREADED:
                        # oneway call to be run inside its own thread
                        thread=threadutil.Thread(target=method, args=vargs, kwargs=kwargs)
                        thread.setDaemon(True)
                        thread.start()
                    elif flags & MessageFactory.FLAGS_ASYNC:
                        future=method(*vargs, **kwargs)
                        self._followFuture(future, client_future)
                    else:
                        isCallback=getattr(method, "_pyroCallback", False)
                        data=method(*vargs, **kwargs)   # this is the actual method call to the Pyro object
            else:
                log.debug("unknown object requested: %s", objId)
                raise errors.DaemonError("unknown object")
            if flags & MessageFactory.FLAGS_ONEWAY:
                return   # oneway call, don't send a response
            elif flags & MessageFactory.FLAGS_ASYNC:
                return  # async call, don't send a response yet
            else:
                data, compressed=self.serializer.serialize(data, compress=Pyro4.config.COMPRESSION)
                flags=0
                if compressed:
                    flags |= MessageFactory.FLAGS_COMPRESSED
                if wasBatched:
                    flags |= MessageFactory.FLAGS_BATCH
                msg=MessageFactory.createMessage(MessageFactory.MSG_RESULT, data, flags, seq)
                del data
                conn.send(msg)
        except Exception as ex:
            xt,xv=sys.exc_info()[0:2]
            if xt is not errors.ConnectionClosedError:
                log.debug("Exception occurred while handling request: %r", xv)
                if client_future is not None:
                    # send exception to the client future
                    client_future.set_exception(ex)
                elif not flags & MessageFactory.FLAGS_ONEWAY:
                    # only return the error to the client if it wasn't a oneway call
                    tblines=util.formatTraceback(detailed=Pyro4.config.DETAILED_TRACEBACK)
                    self._sendExceptionResponse(conn, seq, xv, tblines)
            if isCallback or isinstance(xv, (errors.CommunicationError, errors.SecurityError)):
                raise       # re-raise if flagged as callback, communication or security error.

    def _sendExceptionResponse(self, connection, seq, exc_value, tbinfo):
        """send an exception back including the local traceback info"""
        exc_value._pyroTraceback=tbinfo
        if sys.platform=="cli":
            util.fixIronPythonExceptionForPickle(exc_value, True)  # piggyback attributes
        try:
            data, _=self.serializer.serialize(exc_value)
        except:
            # the exception object couldn't be serialized, use a generic PyroError instead
            xt, xv, tb = sys.exc_info()
            msg = "Error serializing exception: %s. Original exception: %s: %s" % (str(xv), type(exc_value), str(exc_value))
            exc_value = errors.PyroError(msg)
            exc_value._pyroTraceback=tbinfo
            if sys.platform=="cli":
                util.fixIronPythonExceptionForPickle(exc_value, True)  # piggyback attributes
            data, _=self.serializer.serialize(exc_value)
        msg=MessageFactory.createMessage(MessageFactory.MSG_RESULT, data, MessageFactory.FLAGS_EXCEPTION, seq)
        del data
        connection.send(msg)

    def _followFuture(self, future, client_future):
        uri = client_future._pyroUri.asString()
        self._uriToFuture[uri] = future
        def on_future_completion(f):
            try:
                client_future.set_result(f.result())
            except cfutures.CancelledError:
                client_future.set_cancelled()
            except Exception as ex:
                try:
                    client_future.set_exception(ex)
                except: # exception cannot be sent => simplify
                    logging.info("Failed to send full exception, will send summary")
                    msg = "Exception %s %s (Error serializing exception)" % (type(ex), str(ex))
                    exc_value = errors.PyroError(msg)
                    client_future.set_exception(exc_value)
            finally:
                del self._uriToFuture[uri] # that should be the only ref, so kill connection
        future.add_done_callback(on_future_completion)

        if hasattr(future, "add_update_callback"):
            def on_future_progess(f, s, e):
                client_future.set_progress(s, e)
            # called at least once immediately, and once just before completion callback
            future.add_update_callback(on_future_progess)

    def _cancelFuture(self, client_future_uri):
        if client_future_uri in self._uriToFuture:
            future = self._uriToFuture[client_future_uri]
            return future.cancel()
        else:
            log.debug("Couldn't find future %s in %s", client_future_uri, str(self._uriToFuture))
            return False

    def register(self, obj, objectId=None):
        """
        Register a Pyro object under the given id. Note that this object is now only
        known inside this daemon, it is not automatically available in a name server.
        This method returns a URI for the registered object.
        """
        if objectId:
            if not isinstance(objectId, basestring):
                raise TypeError("objectId must be a string or None")
        else:
            objectId="obj_"+uuid.uuid4().hex   # generate a new objectId
        if hasattr(obj, "_pyroId") and obj._pyroId != "":     # check for empty string is needed for Cython
            raise errors.DaemonError("object already has a Pyro id")
        if objectId in self.objectsById:
            raise errors.DaemonError("object already registered with that id")
        # set some pyro attributes
        obj._pyroId=objectId
        obj._pyroDaemon=self
        if Pyro4.config.AUTOPROXY:
            # register a custom serializer for the type to automatically return proxies
            try:
                if isinstance(obj, tuple(self.serializers)):
                    # Find the most fitting serializer by picking the highest in the mro
                    for t in type(obj).__mro__:
                        if t in self.serializers:
                            copyreg.pickle(type(obj), self.serializers[t])
                            break
                else:
                    copyreg.pickle(type(obj),pyroObjectSerializer)
            except TypeError:
                pass
        # register the object in the mapping
        self.objectsById[obj._pyroId]=obj
        return self.uriFor(objectId)

    def unregister(self, objectOrId):
        """
        Remove an object from the known objects inside this daemon.
        You can unregister an object directly or with its id.
        """
        if objectOrId is None:
            raise ValueError("object or objectid argument expected")
        if not isinstance(objectOrId, basestring):
            objectId=getattr(objectOrId, "_pyroId", None)
            if objectId is None:
                raise errors.DaemonError("object isn't registered")
        else:
            objectId=objectOrId
            objectOrId=None
        if objectId==constants.DAEMON_NAME:
            return
        if objectId in self.objectsById:
            del self.objectsById[objectId]
            if objectOrId is not None:
                del objectOrId._pyroId
                del objectOrId._pyroDaemon
                # Don't remove the custom type serializer (copyreg.pickle) because there
                # may be other registered objects of the same type still depending on it.
                # Also, it would require an inefficient linear search through the registered
                # objects map to scan for types. Finally, the copyreg module doesn't seem
                # to be designed with cleanup in mind (it has no explicit unregister function)

    def uriFor(self, objectOrId=None, nat=True):
        """
        Get a URI for the given object (or object id) from this daemon.
        Only a daemon can hand out proper uris because the access location is
        contained in them.
        Note that unregistered objects cannot be given an uri, but unregistered
        object names can (it's just a string we're creating in that case).
        If nat is set to False, the configured NAT address (if any) is ignored and it will
        return an URI for the internal address.
        """
        if not isinstance(objectOrId, basestring):
            objectOrId=getattr(objectOrId, "_pyroId", None)
            if objectOrId is None:
                raise errors.DaemonError("object isn't registered")
        if nat:
            loc=self.natLocationStr or self.locationStr
        else:
            loc=self.locationStr
        return URI("PYRO:%s@%s" % (objectOrId, loc))
        
    def close(self):
        """Close down the server and release resources"""
        log.debug("daemon closing")
        if self.transportServer:
            self.transportServer.close()
            self.transportServer=None

    def __repr__(self):
        return "<%s.%s at 0x%x, %s, %d objects>" % (self.__class__.__module__, self.__class__.__name__,
            id(self), self.locationStr, len(self.objectsById))

    def __enter__(self):
        if not self.transportServer:
            raise errors.PyroError("cannot reuse this object")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def __getstate__(self):
        return {}   # a little hack to make it possible to serialize Pyro objects.