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)
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 = {}
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)
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)
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.