Ejemplo n.º 1
0
 def __init__(self, uri):
     """
     .. autoattribute:: _pyroOneway
     .. autoattribute:: _pyroTimeout
     """
     _check_hmac()  # check if hmac secret key is set
     if isinstance(uri, basestring):
         uri = URI(uri)
     elif not isinstance(uri, URI):
         raise TypeError("expected Pyro URI")
     self._pyroUri = uri
     self._pyroConnection = None
     self._pyroOneway = set()
     self._pyroSeq = 0  # message sequence number
     self.__pyroTimeout = Pyro4.config.COMMTIMEOUT
     self.__pyroLock = threadutil.Lock()
     self.__pyroConnLock = threadutil.Lock()
     util.get_serializer(
         Pyro4.config.SERIALIZER
     )  # assert that the configured serializer is available
     if os.name == "java" and Pyro4.config.SERIALIZER == "marshal":
         import warnings
         warnings.warn(
             "marshal doesn't work correctly with Jython (issue 2077); please choose another serializer",
             RuntimeWarning)
Ejemplo n.º 2
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)
Ejemplo n.º 3
0
 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
Ejemplo n.º 4
0
 def connectionMade(self):
     """
     Handshake - should replicate Pyro4.Daemon._handshake()
     """
     log = logger.debug
     if self.state == "server":
         log("Connection made with Pyro4Protocol")
         log("... attempting handshake")
         ser = util.get_serializer("marshal")
         data = ser.dumps("ok")
         msg = Message(Pyro4.message.MSG_CONNECTOK, data, ser.serializer_id, 0, 1)
         self.transport.write(msg.to_bytes())
         self.state = "header"
Ejemplo n.º 5
0
 def _pyroInvoke(self, methodname, vargs, kwargs, flags=0):
     """perform the remote method call communication"""
     if self._pyroConnection is None:
         # rebind here, don't do it from inside the invoke because deadlock will occur
         self.__pyroCreateConnection()
     serializer = util.get_serializer(Pyro4.config.SERIALIZER)
     data, compressed = serializer.serializeCall(
         self._pyroConnection.objectId, methodname, vargs, kwargs,
         compress=Pyro4.config.COMPRESSION)
     if compressed:
         flags |= Pyro4.message.FLAGS_COMPRESSED
     if methodname in self._pyroOneway:
         flags |= Pyro4.message.FLAGS_ONEWAY
     with self.__pyroLock:
         self._pyroSeq=(self._pyroSeq+1)&0xffff
         if Pyro4.config.LOGWIRE:
             log.debug("proxy wiredata sending: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (Pyro4.message.MSG_INVOKE, flags, serializer.serializer_id, self._pyroSeq, data))
         msg = Message(Pyro4.message.MSG_INVOKE, data, serializer.serializer_id, flags, self._pyroSeq)
         try:
             self._pyroConnection.send(msg.to_bytes())
             del msg  # invite GC to collect the object, don't wait for out-of-scope
             if flags & Pyro4.message.FLAGS_ONEWAY:
                 return None    # oneway call, no response data
             else:
                 msg = Message.recv(self._pyroConnection, [Pyro4.message.MSG_RESULT])
                 if Pyro4.config.LOGWIRE:
                     log.debug("proxy wiredata received: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" % (msg.type, msg.flags, msg.serializer_id, msg.seq, msg.data))
                 self.__pyroCheckSequence(msg.seq)
                 if msg.serializer_id != serializer.serializer_id:
                     error = "invalid serializer in response: %d" % msg.serializer_id
                     log.error(error)
                     raise errors.ProtocolError(error)
                 data = serializer.deserializeData(msg.data, compressed=msg.flags & Pyro4.message.FLAGS_COMPRESSED)
                 if msg.flags & Pyro4.message.FLAGS_EXCEPTION:
                     if sys.platform=="cli":
                         util.fixIronPythonExceptionForPickle(data, False)
                     raise data
                 else:
                     return data
         except (errors.CommunicationError, KeyboardInterrupt):
             # Communication error during read. To avoid corrupt transfers, we close the connection.
             # Otherwise we might receive the previous reply as a result of a new methodcall!
             # Special case for keyboardinterrupt: people pressing ^C to abort the client
             # may be catching the keyboardinterrupt in their code. We should probably be on the
             # safe side and release the proxy connection in this case too, because they might
             # be reusing the proxy object after catching the exception...
             self._pyroRelease()
             raise
Ejemplo n.º 6
0
    def dataReceived(self, data):
        """
        This function must aggregate and standardize the I/O behavior of several Pyro4 functions:

         - Daemon.handleRequest
         - Message.recv
         - Protocol._pyroInvoke

        Due to differences between synchronous and asynchronous approaches, twisted-pyro uses states to determine how
        to route received data.  Because state data need not persist across connections (unlike state information in
        many applications), it is attached to the Protocol.  These states are:

         - server:  indicates that a handshake will be required upon connection
         - header:  waiting on enough data to parse a message header and respond accordingly (idle state for servers)
         - annotations:  header parsed, waiting on amount of annotation data requested in header
         - data:  header parsed, waiting on amount of data requested in header
         - response:  the other end is waiting for data from us (idle state for clients)
        """
        log = logger.debug 
        log("Handling %d bytes of data" % len(data))
        self.data += data
        if self.state == "header" and len(self.data) >= Message.header_size:
            log("... enough data received to process header.")
            # Have enough data to process headers
            self.request = Message.from_header(self.data[:Message.header_size])
            if Pyro4.config.LOGWIRE:
                log("wiredata received: msgtype=%d flags=0x%x ser=%d seq=%d data=%r" %
                    (self.request.type, self.request.flags, self.request.serializer_id, self.request.seq, self.request.data))
            if self.required_message_types and self.request.type not in self.required_message_types:
                err = "invalid msg type %d received" % self.request.type
                logger.error(err)
                self._return_error(errors.ProtocolError(err))
            if self.request.serializer_id not in \
                    set([util.get_serializer(ser_name).serializer_id
                         for ser_name in Pyro4.config.SERIALIZERS_ACCEPTED]):
                self._return_error(errors.ProtocolError("message used serializer that is not accepted: %d" % self.request.serializer_id))
            self.data = self.data[Message.header_size:]
            if self.request.annotations_size:
                self.state = "annotations"
            else:
                self.state = "data"

        if self.state == "annotations" and len(self.data) >= self.request.annotations_size:
            log("... enough data received to process annotation.")
            self.request.annotations = {}
            annotations_data = self.data[:self.request.annotations_size]
            self.data = self.data[self.request.annotations_size:]
            i = 0
            while i < self.request.annotations_size:
                anno, length = struct.unpack("!4sH", annotations_data[i:i+6])
                self.request.annotations[anno] = annotations_data[i+6:i+6+length]
                i += 6+length
            if b"HMAC" in self.request.annotations and Pyro4.config.HMAC_KEY:
                if self.request.annotations[b"HMAC"] != self.request.hmac():
                    self._return_error(errors.SecurityError("message hmac mismatch"))
            elif (b"HMAC" in self.request.annotations) != bool(Pyro4.config.HMAC_KEY):
                # Message contains hmac and local HMAC_KEY not set, or vice versa. This is not allowed.
                err = "hmac key config not symmetric"
                logger.warning(err)
                self._return_error(errors.SecurityError(err))
            self.state = "data"

        if self.state == "data" and len(self.data) >= self.request.data_size:
            log("... enough data received to process data.")
            # A oneway call can be immediately followed by another call.  Otherwise, we should not receive any
            # additional data until we have sent a response
            if self.request.flags & Pyro4.message.FLAGS_ONEWAY:
                if self.request.type == message.MSG_PING:
                    error_msg = "ONEWAY ping doesn't make sense"
                    self._return_error(errors.ProtocolError(error_msg))
            else:
                if len(self.data) > self.request.data_size:
                    self.transport.loseConnection()
                    error_msg = "max message size exceeded (%d where max=%d)" % \
                                (self.request.data_size + self.request.annotations_size, Pyro4.config.MAX_MESSAGE_SIZE)
                    self._return_error(errors.ProtocolError(error_msg))

            # Transfer data to message
            self.request.data = self.data[:self.request.data_size]
            self.data = self.data[self.request.data_size:]

            # Execute message
            d = Deferred()

            if self.request.type == message.MSG_CONNECT:
                raise NotImplementedError("No action provided for MSG_CONNECT")

            elif self.request.type == message.MSG_CONNECTOK:
                # We only reach this spot if it is a valid message type so we're in client handshake mode.  Update
                # protocol to support client __call__ execution (i.e. invocation)
                self.required_message_types = [message.MSG_CONNECTOK]
                raise NotImplementedError("No action provided for MSG_CONNECTOK")

            elif self.request.type == message.MSG_CONNECTFAIL:
                raise NotImplementedError("No action provided for MSG_CONNECTFAIL")

            elif self.request.type == message.MSG_INVOKE:
                log("Responding to invoke request.")
                # Must be a static method so the Protocol can reset after oneway messages
                d.addCallback(self._pyro_remote_call)
                reactor.callLater(0, d.callback, self.request)

            elif self.request.type == message.MSG_PING:
                log("Responding to ping request.")
                reactor.callLater(0, d.callback, b"pong")

            elif self.request.type == message.MSG_RESULT:
                # Trigger callback with data or raise exception
                raise NotImplementedError("No action provided for MSG_RESULT")

            if self.request.flags & Pyro4.message.FLAGS_ONEWAY:
                log("... ONEWAY request, not building response.")

                def reraise(response):
                    if isinstance(response, Exception):
                        log.exception("ONEWAY call resulted in an exception: %s" % str(response))
                        return Failure(response)
                d.addCallback(reraise)

                # In Pyro4 core, the ONEWAY_THREADED option determines whether or not the execution order of ONEWAY
                # calls is guaranteed. To replicate this behavior, we reference the same flag.
                if Pyro4.config.ONEWAY_THREADED:
                    self.reset()
                else:
                    self.state = "blocked"

                    def process_next_message(response):
                        self.reset()
                        return response
                    d.addBoth(process_next_message)
            else:
                log("... appending response callbacks.")
                # If the previous call was not oneway, we maintain state on the protocol
                log("... setting state to 'response'")
                self.state = "response"
                log("... adding build/send callbacks")
                d.addCallback(self._build_response)
                d.addCallback(self._send_response)

        if self.state == "response" and len(self.data) > 0:
            error_msg = "data received while in response state" % \
                        (self.request.data_size + self.request.annotations_size, Pyro4.config.MAX_MESSAGE_SIZE)
            self._return_error(errors.ProtocolError(error_msg))

        if self.state == "blocked":
            # Waiting on a ONEWAY call (with guaranteed execution order) to complete.  When it completes, the system
            # will reset itself to accept the next call.
            pass