From df77ec538b3d3b460f9e3dc17541854a63cd2a61 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 16 Apr 2020 22:00:13 +0200 Subject: [PATCH] fixed circular import and empty ns hostname lookup --- Pyro5/__init__.py | 2 +- Pyro5/api.py | 3 +- Pyro5/callcontext.py | 47 ++++++++++++++++++++++++++++++ Pyro5/client.py | 13 +++++---- Pyro5/compatibility/Pyro4.py | 5 ---- Pyro5/core.py | 56 +++--------------------------------- Pyro5/nsc.py | 2 +- Pyro5/protocol.py | 8 ++---- Pyro5/server.py | 34 +++++++++++----------- Pyro5/utils/httpgateway.py | 12 ++++---- docs/source/changelog.rst | 6 ++++ examples/handshake/server.py | 2 +- tests/test_api.py | 3 +- 13 files changed, 97 insertions(+), 96 deletions(-) create mode 100644 Pyro5/callcontext.py diff --git a/Pyro5/__init__.py b/Pyro5/__init__.py index 4d97dcf..c4a5826 100644 --- a/Pyro5/__init__.py +++ b/Pyro5/__init__.py @@ -4,7 +4,7 @@ Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ -__version__ = "5.9" +__version__ = "5.9.1" __author__ = "Irmen de Jong" diff --git a/Pyro5/api.py b/Pyro5/api.py index d10d176..9446b7b 100644 --- a/Pyro5/api.py +++ b/Pyro5/api.py @@ -6,11 +6,12 @@ from . import __version__ from .configure import global_config as config -from .core import URI, locate_ns, resolve, type_meta, current_context +from .core import URI, locate_ns, resolve, type_meta from .client import Proxy, BatchProxy, SerializedBlob from .server import Daemon, DaemonObject, callback, expose, behavior, oneway, serve from .nameserver import start_ns, start_ns_loop from .serializers import SerializerBase +from .callcontext import current_context __all__ = ["config", "URI", "locate_ns", "resolve", "type_meta", "current_context", diff --git a/Pyro5/callcontext.py b/Pyro5/callcontext.py new file mode 100644 index 0000000..d76e2bc --- /dev/null +++ b/Pyro5/callcontext.py @@ -0,0 +1,47 @@ +import threading +from . import errors + + +# call context thread local +class _CallContext(threading.local): + def __init__(self): + # per-thread initialization + self.client = None + self.client_sock_addr = None + self.seq = 0 + self.msg_flags = 0 + self.serializer_id = 0 + self.annotations = {} + self.response_annotations = {} + self.correlation_id = None + + def to_global(self): + return dict(self.__dict__) + + def from_global(self, values): + self.client = values["client"] + self.seq = values["seq"] + self.msg_flags = values["msg_flags"] + self.serializer_id = values["serializer_id"] + self.annotations = values["annotations"] + self.response_annotations = values["response_annotations"] + self.correlation_id = values["correlation_id"] + self.client_sock_addr = values["client_sock_addr"] + + def track_resource(self, resource): + """keep a weak reference to the resource to be tracked for this connection""" + if self.client: + self.client.tracked_resources.add(resource) + else: + raise errors.PyroError("cannot track resource on a connectionless call") + + def untrack_resource(self, resource): + """no longer track the resource for this connection""" + if self.client: + self.client.tracked_resources.discard(resource) + else: + raise errors.PyroError("cannot untrack resource on a connectionless call") + + +current_context = _CallContext() +"""the context object for the current call. (thread-local)""" diff --git a/Pyro5/client.py b/Pyro5/client.py index 523af86..ca0e137 100644 --- a/Pyro5/client.py +++ b/Pyro5/client.py @@ -9,6 +9,7 @@ import serpent import contextlib from . import config, core, serializers, protocol, errors, socketutil +from .callcontext import current_context try: from greenlet import getcurrent as get_ident except ImportError: @@ -71,7 +72,7 @@ def __init__(self, uri, connected_socket=None): # note: we're not clearing the client annotations dict here. # that is because otherwise it will be wiped if a new proxy is needed to connect PYRONAME uris. # clearing the response annotations is okay. - core.current_context.response_annotations = {} + current_context.response_annotations = {} if connected_socket: self.__pyroCreateConnection(False, connected_socket) @@ -196,12 +197,12 @@ def __pyroSetTimeout(self, timeout): def _pyroInvoke(self, methodname, vargs, kwargs, flags=0, objectId=None): """perform the remote method call communication""" self.__check_owner() - core.current_context.response_annotations = {} + current_context.response_annotations = {} if self._pyroConnection is None: self.__pyroCreateConnection() serializer = serializers.serializers[self._pyroSerializer or config.SERIALIZER] objectId = objectId or self._pyroConnection.objectId - annotations = core.current_context.annotations + annotations = current_context.annotations if vargs and isinstance(vargs[0], SerializedBlob): # special serialization of a 'blob' that stays serialized data, flags = self.__serializeBlobArgs(vargs, kwargs, annotations, flags, objectId, methodname, serializer) @@ -229,7 +230,7 @@ def _pyroInvoke(self, methodname, vargs, kwargs, flags=0, objectId=None): log.error(error) raise errors.SerializeError(error) if msg.annotations: - core.current_context.response_annotations = msg.annotations + current_context.response_annotations = msg.annotations if self._pyroRawWireResponse: return msg data = serializer.loads(msg.data) @@ -285,7 +286,7 @@ def connect_and_handshake(conn): data = {"handshake": self._pyroHandshake, "object": uri.object} data = serializer.dumps(data) msg = protocol.SendingMessage(protocol.MSG_CONNECT, 0, self._pyroSeq, serializer.serializer_id, - data, annotations=core.current_context.annotations) + data, annotations=current_context.annotations) if config.LOGWIRE: protocol.log_wiredata(log, "proxy connect sending", msg) conn.send(msg.data) @@ -320,7 +321,7 @@ def connect_and_handshake(conn): self._pyroValidateHandshake(handshake_response) log.debug("connected to %s - %s - %s", self._pyroUri, conn.family(), "SSL" if sslContext else "unencrypted") if msg.annotations: - core.current_context.response_annotations = msg.annotations + current_context.response_annotations = msg.annotations else: conn.close() err = "cannot connect to %s: invalid msg type %d received" % (connect_location, msg.type) diff --git a/Pyro5/compatibility/Pyro4.py b/Pyro5/compatibility/Pyro4.py index 8f7321e..62ee7d8 100644 --- a/Pyro5/compatibility/Pyro4.py +++ b/Pyro5/compatibility/Pyro4.py @@ -19,11 +19,6 @@ Pyro - Python Remote Objects. Copyright by Irmen de Jong (irmen@razorvine.net). """ -# the symbols that were available in Pyro4 as Pyro4.* : -# from Pyro4.core import URI, Proxy, Daemon, callback, batch, asyncproxy, oneway, expose, behavior, current_context -# from Pyro4.core import _locateNS as locateNS, _resolve as resolve -# from Pyro4.futures import Future - import sys import ipaddress diff --git a/Pyro5/core.py b/Pyro5/core.py index dc95e17..1578a96 100644 --- a/Pyro5/core.py +++ b/Pyro5/core.py @@ -9,14 +9,13 @@ import contextlib import ipaddress import socket -import threading import random import serpent from typing import Union, Optional from . import config, errors, socketutil, serializers, nameserver -__all__ = ["URI", "DAEMON_NAME", "NAMESERVER_NAME", "current_context", "resolve", "locate_ns", "type_meta"] +__all__ = ["URI", "DAEMON_NAME", "NAMESERVER_NAME", "resolve", "locate_ns", "type_meta"] log = logging.getLogger("Pyro5.core") @@ -203,13 +202,11 @@ def resolve(uri: Union[str, URI], delay_time: float = 0.0) -> URI: raise errors.PyroError("invalid uri protocol") -from . import client # circular import... - - def locate_ns(host: Union[str, ipaddress.IPv4Address, ipaddress.IPv6Address] = "", - port: Optional[int] = None, broadcast: bool = True) -> client.Proxy: + port: Optional[int] = None, broadcast: bool = True) -> "client.Proxy": """Get a proxy for a name server somewhere in the network.""" - if host == "": + from . import client + if not host: # first try localhost if we have a good chance of finding it there if config.NS_HOST in ("localhost", "::1") or config.NS_HOST.startswith("127."): if ":" in config.NS_HOST: # ipv6 @@ -295,48 +292,3 @@ def type_meta(class_or_object, prefix="class:"): if hasattr(class_or_object, "__class__"): return type_meta(class_or_object.__class__) return frozenset() - - -# call context thread local -class _CallContext(threading.local): - def __init__(self): - # per-thread initialization - self.client = None - self.client_sock_addr = None - self.seq = 0 - self.msg_flags = 0 - self.serializer_id = 0 - self.annotations = {} - self.response_annotations = {} - self.correlation_id = None - - def to_global(self): - return dict(self.__dict__) - - def from_global(self, values): - self.client = values["client"] - self.seq = values["seq"] - self.msg_flags = values["msg_flags"] - self.serializer_id = values["serializer_id"] - self.annotations = values["annotations"] - self.response_annotations = values["response_annotations"] - self.correlation_id = values["correlation_id"] - self.client_sock_addr = values["client_sock_addr"] - - def track_resource(self, resource): - """keep a weak reference to the resource to be tracked for this connection""" - if self.client: - self.client.tracked_resources.add(resource) - else: - raise errors.PyroError("cannot track resource on a connectionless call") - - def untrack_resource(self, resource): - """no longer track the resource for this connection""" - if self.client: - self.client.tracked_resources.discard(resource) - else: - raise errors.PyroError("cannot untrack resource on a connectionless call") - - -current_context = _CallContext() -"""the context object for the current call. (thread-local)""" diff --git a/Pyro5/nsc.py b/Pyro5/nsc.py index a4ae356..4dea459 100644 --- a/Pyro5/nsc.py +++ b/Pyro5/nsc.py @@ -104,7 +104,7 @@ def cmd_yplookup_any(): def main(args=None): from argparse import ArgumentParser parser = ArgumentParser(description="Pyro name server control utility.") - parser.add_argument("-n", "--host", dest="host", help="hostname of the NS") + parser.add_argument("-n", "--host", dest="host", help="hostname of the NS", default="") parser.add_argument("-p", "--port", dest="port", type=int, help="port of the NS (or bc-port if host isn't specified)") parser.add_argument("-u", "--unixsocket", help="Unix domain socket name of the NS") parser.add_argument("-v", "--verbose", action="store_true", dest="verbose", help="verbose output") diff --git a/Pyro5/protocol.py b/Pyro5/protocol.py index c16acc4..b8d1228 100644 --- a/Pyro5/protocol.py +++ b/Pyro5/protocol.py @@ -38,6 +38,7 @@ import zlib import uuid from . import config, errors +from .callcontext import current_context log = logging.getLogger("Pyro5.protocol") @@ -66,9 +67,6 @@ _empty_correlation_id = b"\0" * 16 -from . import core # circular import... - - class SendingMessage: """Wire protocol message that will be sent.""" @@ -86,9 +84,9 @@ def __init__(self, msgtype, flags, seq, serializer_id, payload, annotations=None total_size = len(payload) + annotations_size if total_size > config.MAX_MESSAGE_SIZE: raise errors.ProtocolError("message too large ({:d}, max={:d})".format(total_size, config.MAX_MESSAGE_SIZE)) - if core.current_context.correlation_id: + if current_context.correlation_id: flags |= FLAGS_CORR_ID - self.corr_id = core.current_context.correlation_id.bytes + self.corr_id = current_context.correlation_id.bytes else: self.corr_id = _empty_correlation_id header_data = struct.pack(_header_format, b"PYRO", PROTOCOL_VERSION, msgtype, serializer_id, flags, seq, diff --git a/Pyro5/server.py b/Pyro5/server.py index e545eab..ca2c4ce 100644 --- a/Pyro5/server.py +++ b/Pyro5/server.py @@ -18,7 +18,7 @@ import ipaddress from typing import Callable, Tuple, Union, Optional, Dict, Any, Sequence, Set from . import config, core, errors, serializers, socketutil, protocol, client - +from .callcontext import current_context __all__ = ["Daemon", "DaemonObject", "callback", "expose", "behavior", "oneway", "serve"] @@ -175,7 +175,7 @@ def get_next_stream_item(self, streamId): client, timestamp, linger_timestamp, stream = self.daemon.streaming_responses[streamId] if client is None: # reset client connection association (can be None if proxy disconnected) - self.daemon.streaming_responses[streamId] = (core.current_context.client, timestamp, 0, stream) + self.daemon.streaming_responses[streamId] = (current_context.client, timestamp, 0, stream) try: return next(stream) except Exception: @@ -331,9 +331,9 @@ def _handshake(self, conn, denied_reason=None): if config.LOGWIRE: protocol.log_wiredata(log, "daemon handshake received", msg) if msg.flags & protocol.FLAGS_CORR_ID: - core.current_context.correlation_id = uuid.UUID(bytes=msg.corr_id) + current_context.correlation_id = uuid.UUID(bytes=msg.corr_id) else: - core.current_context.correlation_id = uuid.uuid4() + current_context.correlation_id = uuid.uuid4() serializer_id = msg.serializer_id serializer = serializers.serializers_by_id[serializer_id] data = serializer.loads(msg.data) @@ -397,9 +397,9 @@ def handleRequest(self, conn): request_seq = msg.seq request_serializer_id = msg.serializer_id if msg.flags & protocol.FLAGS_CORR_ID: - core.current_context.correlation_id = uuid.UUID(bytes=msg.corr_id) + current_context.correlation_id = uuid.UUID(bytes=msg.corr_id) else: - core.current_context.correlation_id = uuid.uuid4() + current_context.correlation_id = uuid.uuid4() if config.LOGWIRE: protocol.log_wiredata(log, "daemon wiredata received", msg) if msg.type == protocol.MSG_PING: @@ -416,16 +416,16 @@ def handleRequest(self, conn): else: # normal deserialization of remote call arguments objId, method, vargs, kwargs = serializer.loadsCall(msg.data) - core.current_context.client = conn + current_context.client = conn try: # store, because on oneway calls, socket will be disconnected: - core.current_context.client_sock_addr = conn.sock.getpeername() + current_context.client_sock_addr = conn.sock.getpeername() except socket.error: - core.current_context.client_sock_addr = None # sometimes getpeername() doesn't work... - core.current_context.seq = msg.seq - core.current_context.annotations = msg.annotations - core.current_context.msg_flags = msg.flags - core.current_context.serializer_id = msg.serializer_id + current_context.client_sock_addr = None # sometimes getpeername() doesn't work... + current_context.seq = msg.seq + current_context.annotations = msg.annotations + current_context.msg_flags = msg.flags + current_context.serializer_id = msg.serializer_id 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: @@ -489,7 +489,7 @@ def handleRequest(self, conn): response_flags |= protocol.FLAGS_BATCH msg = protocol.SendingMessage(protocol.MSG_RESULT, response_flags, request_seq, serializer.serializer_id, data, annotations=self.__annotations()) - core.current_context.response_annotations = {} + current_context.response_annotations = {} if config.LOGWIRE: protocol.log_wiredata(log, "daemon wiredata sending", msg) conn.send(msg.data) @@ -753,7 +753,7 @@ def combine(self, daemon): self.transportServer.combine_loop(daemon.transportServer) def __annotations(self): - annotations = core.current_context.response_annotations + annotations = current_context.response_annotations annotations.update(self.annotations()) return annotations @@ -965,7 +965,7 @@ class _OnewayCallThread(threading.Thread): def __init__(self, pyro_method, vargs, kwargs, pyro_daemon, pyro_client_sock): super(_OnewayCallThread, self).__init__(target=self._methodcall, name="oneway-call") self.daemon = True - self.parent_context = core.current_context.to_global() + self.parent_context = current_context.to_global() self.pyro_daemon = pyro_daemon self.pyro_client_sock = pyro_client_sock self.pyro_method = pyro_method @@ -973,7 +973,7 @@ def __init__(self, pyro_method, vargs, kwargs, pyro_daemon, pyro_client_sock): self.pyro_kwars = kwargs def run(self): - core.current_context.from_global(self.parent_context) + current_context.from_global(self.parent_context) super(_OnewayCallThread, self).run() def _methodcall(self): diff --git a/Pyro5/utils/httpgateway.py b/Pyro5/utils/httpgateway.py index 492521e..6270317 100644 --- a/Pyro5/utils/httpgateway.py +++ b/Pyro5/utils/httpgateway.py @@ -30,7 +30,7 @@ from wsgiref.simple_server import make_server from argparse import ArgumentParser import traceback -from .. import __version__, config, errors, client, core, protocol, serializers +from .. import __version__, config, errors, client, core, protocol, serializers, callcontext __all__ = ["pyro_app", "main"] @@ -219,9 +219,9 @@ def process_pyro_request(environ, path, parameters, start_response): with client.Proxy(uri) as proxy: header_corr_id = environ.get("HTTP_X_PYRO_CORRELATION_ID", "") if header_corr_id: - core.current_context.correlation_id = uuid.UUID(header_corr_id) # use the correlation id from the request header + callcontext.current_context.correlation_id = uuid.UUID(header_corr_id) # use the correlation id from the request header else: - core.current_context.correlation_id = uuid.uuid4() # set new correlation id + callcontext.current_context.correlation_id = uuid.uuid4() # set new correlation id proxy._pyroGetMetadata() if "oneway" in pyro_options: proxy._pyroOneway.add(method) @@ -229,7 +229,7 @@ def process_pyro_request(environ, path, parameters, start_response): result = {"methods": tuple(proxy._pyroMethods), "attributes": tuple(proxy._pyroAttrs)} reply = json.dumps(result).encode("utf-8") start_response('200 OK', [('Content-Type', 'application/json; charset=utf-8'), - ('X-Pyro-Correlation-Id', str(core.current_context.correlation_id))]) + ('X-Pyro-Correlation-Id', str(callcontext.current_context.correlation_id))]) return [reply] else: proxy._pyroRawWireResponse = True # we want to access the raw response json @@ -243,7 +243,7 @@ def process_pyro_request(environ, path, parameters, start_response): if msg is None or "oneway" in pyro_options: # was a oneway call, no response available start_response('200 OK', [('Content-Type', 'application/json; charset=utf-8'), - ('X-Pyro-Correlation-Id', str(core.current_context.correlation_id))]) + ('X-Pyro-Correlation-Id', str(callcontext.current_context.correlation_id))]) return [] elif msg.flags & protocol.FLAGS_EXCEPTION: # got an exception response so send a 500 status @@ -252,7 +252,7 @@ def process_pyro_request(environ, path, parameters, start_response): else: # normal response start_response('200 OK', [('Content-Type', 'application/json; charset=utf-8'), - ('X-Pyro-Correlation-Id', str(core.current_context.correlation_id))]) + ('X-Pyro-Correlation-Id', str(callcontext.current_context.correlation_id))]) return [msg.data] except Exception as x: stderr = environ["wsgi.errors"] diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 232220d..b55d877 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -2,6 +2,12 @@ Change Log ********** +**Pyro 5.9.1** + +- fixed some circular import conflicts +- fixed empty nameserver host lookup issue + + **Pyro 5.9** - added privilege-separation example diff --git a/examples/handshake/server.py b/examples/handshake/server.py index 144663b..8812b80 100644 --- a/examples/handshake/server.py +++ b/examples/handshake/server.py @@ -9,7 +9,7 @@ class CustomDaemon(Daemon): def validateHandshake(self, conn, data): print("Daemon received handshake request from:", conn.sock.getpeername()) print("Handshake data:", data) - # if needed, you can inspect Pyro5.core.current_context + # if needed, you can inspect Pyro5.callcontext.current_context if data == secret_code: print("Secret code okay! Connection accepted.") # return some custom handshake data: diff --git a/tests/test_api.py b/tests/test_api.py index edfe35d..a1f34bb 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -3,6 +3,7 @@ import Pyro5.client import Pyro5.server import Pyro5.nameserver +import Pyro5.callcontext def test_api(): @@ -12,4 +13,4 @@ def test_api(): assert Pyro5.api.Proxy is Pyro5.client.Proxy assert Pyro5.api.Daemon is Pyro5.server.Daemon assert Pyro5.api.start_ns is Pyro5.nameserver.start_ns - assert Pyro5.api.current_context is Pyro5.core.current_context + assert Pyro5.api.current_context is Pyro5.callcontext.current_context