Beispiel #1
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":
             raise NotImplementedError(
                 "select or poll-based server is not supported for jython, use thread server instead"
             )
         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)
Beispiel #2
0
 def testServer_poll(self):
     daemon=ServerCallback()
     port=SU.findProbablyUnusedPort()
     serv=SocketServer_Poll()
     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)
Beispiel #3
0
 def testServer_poll(self):
     daemon = ServerCallback()
     port = SU.findProbablyUnusedPort()
     serv = SocketServer_Poll()
     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)
Beispiel #4
0
class Daemon(object):
    """
    Pyro daemon. Contains server side logic and dispatches incoming remote method calls
    to the appropriate objects.
    """
    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":
                raise NotImplementedError(
                    "select or poll-based server is not supported for jython, use thread server instead"
                )
            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)

    @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.
        # We default to the marshal serializer to send message payload of "ok"
        ser = util.get_serializer("marshal")
        data = ser.dumps("ok")
        msg = Message(Pyro4.message.MSG_CONNECTOK, data, ser.serializer_id, 0,
                      1)
        conn.send(msg.to_bytes())
        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.
        """
        request_flags = 0
        request_seq = 0
        request_serializer_id = util.MarshalSerializer.serializer_id
        wasBatched = False
        isCallback = False
        try:
            msg = Message.recv(
                conn, [Pyro4.message.MSG_INVOKE, Pyro4.message.MSG_PING])
            request_flags = msg.flags
            request_seq = msg.seq
            request_serializer_id = msg.serializer_id
            if Pyro4.config.LOGWIRE:
                log.debug(
                    "daemon wiredata received: msgtype=%d flags=0x%x ser=%d seq=%d data=%r"
                    % (msg.type, msg.flags, msg.serializer_id, msg.seq,
                       msg.data))
            if msg.type == Pyro4.message.MSG_PING:
                # return same seq, but ignore any data (it's a ping, not an echo). Nothing is deserialized.
                msg = Message(Pyro4.message.MSG_PING, b"pong",
                              msg.serializer_id, 0, msg.seq)
                if Pyro4.config.LOGWIRE:
                    log.debug(
                        "daemon wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r"
                        % (msg.type, msg.flags, msg.serializer_id, msg.seq,
                           msg.data))
                conn.send(msg.to_bytes())
                return
            if msg.serializer_id not in self.__serializer_ids:
                raise errors.ProtocolError(
                    "message used serializer that is not accepted: %d" %
                    msg.serializer_id)
            serializer = util.get_serializer_by_id(msg.serializer_id)
            objId, method, vargs, kwargs = serializer.deserializeCall(
                msg.data,
                compressed=msg.flags & Pyro4.message.FLAGS_COMPRESSED)
            del msg  # invite GC to collect the object, don't wait for out-of-scope
            obj = self.objectsById.get(objId)
            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 request_flags & Pyro4.message.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
                else:
                    # normal single method call
                    method = util.resolveDottedAttribute(
                        obj, method, Pyro4.config.DOTTEDNAMES)
                    if request_flags & Pyro4.message.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()
                    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 request_flags & Pyro4.message.FLAGS_ONEWAY:
                return  # oneway call, don't send a response
            else:
                data, compressed = serializer.serializeData(
                    data, compress=Pyro4.config.COMPRESSION)
                response_flags = 0
                if compressed:
                    response_flags |= Pyro4.message.FLAGS_COMPRESSED
                if wasBatched:
                    response_flags |= Pyro4.message.FLAGS_BATCH
                if Pyro4.config.LOGWIRE:
                    log.debug(
                        "daemon wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r"
                        % (Pyro4.message.MSG_RESULT, response_flags,
                           serializer.serializer_id, request_seq, data))
                msg = Message(Pyro4.message.MSG_RESULT, data,
                              serializer.serializer_id, response_flags,
                              request_seq)
                conn.send(msg.to_bytes())
        except Exception:
            xt, xv = sys.exc_info()[0:2]
            if xt is not errors.ConnectionClosedError:
                log.debug("Exception occurred while handling request: %r", xv)
                if not request_flags & Pyro4.message.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, request_seq,
                                                request_serializer_id, 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, serializer_id, 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
        serializer = util.get_serializer_by_id(serializer_id)
        try:
            data, compressed = serializer.serializeData(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, compressed = serializer.serializeData(exc_value)
        flags = Pyro4.message.FLAGS_EXCEPTION
        if compressed:
            flags |= Pyro4.message.FLAGS_COMPRESSED
        if Pyro4.config.LOGWIRE:
            log.debug(
                "daemon wiredata sending (error response): msgtype=%d flags=0x%x ser=%d seq=%d data=%r"
                % (Pyro4.message.MSG_RESULT, flags, serializer.serializer_id,
                   seq, data))
        msg = Message(Pyro4.message.MSG_RESULT, data, serializer.serializer_id,
                      flags, seq)
        connection.send(msg.to_bytes())

    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
            # we need to do this for all known serializers
            for ser in util._serializers.values():
                ser.register_type_replacement(type(obj), pyroObjectToAutoProxy)
        # 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 because there may be
                # other registered objects of the same type still depending on it.

    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, because they can reference a daemon

    def __getstate_for_dict__(self):
        return self.__getstate__()