Esempio n. 1
0
    def __init__(self, uri, key_file=None, cert_file=None, ca_certs=None, validate_cert_hostname=True,
                 extra_headers=None, timeout=None, pool_connections=False, ssl_opts=None):
        """
        :param uri: The endpoint JSON-RPC server URL.
        :param key_file: (Deprecated) Secret key to use for ssl connection.
        :param cert_file: (Deprecated) Cert to send to server for ssl connection.
        :param ca_certs: (Deprecated) File containing concatenated list of certs to validate server cert against.
        :param extra_headers: Any additional headers to include with all requests.
        :param pool_connections: Whether to use a thread-local connection pool for connections.
        :param ssl_opts: Dictionary of options passed to ssl.wrap_socket
        """
        self.logger = logging.getLogger('{0.__module__}.{0.__name__}'.format(self.__class__))
        if extra_headers is None:
            extra_headers = {}

        parsed_uri = urlparse(uri)

        self.type = parsed_uri.scheme
        if self.type not in ("http", "https"):
            raise JsonRpcError("unsupported JSON-RPC uri: %s" % uri)

        self.handler = parsed_uri.path
        port = parsed_uri.port or (80 if self.type == 'http' else 443)
        self.host = '{}:{}'.format(parsed_uri.hostname, port)

        if parsed_uri.username and parsed_uri.password:
            auth = '{}:{}'.format(parsed_uri.username, parsed_uri.password)
            auth = base64.encodestring(unquote(auth).encode('ascii'))
            auth = auth.strip()
            extra_headers.update({"Authorization": b"Basic " + auth})

        self.validate_cert_hostname = validate_cert_hostname
        self.ssl_opts = ssl_opts or {}
        deprecated_params = {'keyfile': key_file, 'certfile': cert_file, 'ca_certs': ca_certs}
        for opt, val in deprecated_params.items():
            if val is not None:
                warnings.warn('key_file, cert_file, and ca_certs arguments are deprecated; use ssl_opts argument instead', DeprecationWarning)
                self.ssl_opts.setdefault(opt, val)

        # TODO: This could probably be a little cleaner :)
        if pool_connections:
            if self.type == "https":
                self.transport = TLSConnectionPoolSafeTransport(timeout=timeout)
            else:
                self.transport = TLSConnectionPoolTransport(timeout=timeout)
        else:
            if self.type == "https":
                self.transport = SafeTransport(timeout=timeout, ssl_opts=self.ssl_opts, validate_cert_hostname=self.validate_cert_hostname)
            else:
                self.transport = Transport(timeout=timeout)

        self.extra_headers = extra_headers
        self.id = 0  # Initialize our request ID (gets incremented for every request)
Esempio n. 2
0
    def __init__(self, uri, key_file=None, cert_file=None, ca_certs=None, validate_cert_hostname=True,
                 extra_headers=None, timeout=None, pool_connections=False):
        """
        :param uri: The endpoint JSON-RPC server URL.
        :param key_file: Secret key to use for ssl connection.
        :param cert_file: Cert to send to server for ssl connection.
        :param ca_certs: File containing concatenated list of certs to validate server cert against.
        :param extra_headers: Any additional headers to include with all requests.
        :param pool_connections: Whether to use a thread-local connection pool for connections.
        """
        self.logger = logging.getLogger('{0.__module__}.{0.__name__}'.format(self.__class__))
        if extra_headers is None:
            extra_headers = {}

        parsed_uri = urlparse(uri)

        self.type = parsed_uri.scheme
        if self.type not in ("http", "https"):
            raise JsonRpcError("unsupported JSON-RPC uri: %s" % uri)

        self.handler = parsed_uri.path
        self.host = parsed_uri.hostname

        if parsed_uri.username and parsed_uri.password:
            auth = '{}:{}'.format(parsed_uri.username, parsed_uri.password)
            auth = base64.encodestring(unquote(auth).encode('ascii'))
            auth = auth.strip()
            extra_headers.update({"Authorization": b"Basic " + auth})

        self.key_file = key_file
        self.cert_file = cert_file
        self.ca_certs = ca_certs
        self.validate_cert_hostname = validate_cert_hostname

        # TODO: This could probably be a little cleaner :)
        if pool_connections:
            if self.type == "https":
                self.transport = TLSConnectionPoolSafeTransport(timeout=timeout)
            else:
                self.transport = TLSConnectionPoolTransport(timeout=timeout)
        else:
            if self.type == "https":
                self.transport = SafeTransport(key_file=self.key_file, cert_file=self.cert_file,
                    ca_certs=self.ca_certs, validate_cert_hostname=self.validate_cert_hostname)
            else:
                self.transport = Transport(timeout=timeout)

        self.extra_headers = extra_headers
        self.id = 0 # Initialize our request ID (gets incremented for every request)
Esempio n. 3
0
class ServerProxy(object):
    """
    The ServerProxy provides a proxy to the remote JSON-RPC service methods and performs the
    request encoding and decoding.

    This class uses instance variables to save state and is NOT THREAD-SAFE.

    :ivar type: The scheme we're using for connection: 'http' or 'https'
    :type type: C{str}

    :ivar host: The host we're connecting to (may include port, e.g. "foobar.com:8080")
    :type host: C{str}

    :ivar transport: A L{Transport} instance to use.
    :type transport: L{Transport}

    :ivar id: The JSON-RPC 'id' field (incrementing integer).
    :type id: C{int}

    :ivar extra_headers: Any HTTP request headers that should be sent with every request.
    :type extra_headers: C{dict}

    :ivar method_class: The proxy class for the remote methods (default is L{_Method}).
    :type method_class: C{type}
    """

    method_class = _Method

    def __init__(self, uri, key_file=None, cert_file=None, ca_certs=None, validate_cert_hostname=True,
                 extra_headers=None, timeout=None, pool_connections=False, ssl_opts=None):
        """
        :param uri: The endpoint JSON-RPC server URL.
        :param key_file: (Deprecated) Secret key to use for ssl connection.
        :param cert_file: (Deprecated) Cert to send to server for ssl connection.
        :param ca_certs: (Deprecated) File containing concatenated list of certs to validate server cert against.
        :param extra_headers: Any additional headers to include with all requests.
        :param pool_connections: Whether to use a thread-local connection pool for connections.
        :param ssl_opts: Dictionary of options passed to ssl.wrap_socket
        """
        self.logger = logging.getLogger('{0.__module__}.{0.__name__}'.format(self.__class__))
        if extra_headers is None:
            extra_headers = {}

        parsed_uri = urlparse(uri)

        self.type = parsed_uri.scheme
        if self.type not in ("http", "https"):
            raise JsonRpcError("unsupported JSON-RPC uri: %s" % uri)

        self.handler = parsed_uri.path
        port = parsed_uri.port or (80 if self.type == 'http' else 443)
        self.host = '{}:{}'.format(parsed_uri.hostname, port)

        if parsed_uri.username and parsed_uri.password:
            auth = '{}:{}'.format(parsed_uri.username, parsed_uri.password)
            auth = base64.encodestring(unquote(auth).encode('ascii'))
            auth = auth.strip()
            extra_headers.update({"Authorization": b"Basic " + auth})

        self.validate_cert_hostname = validate_cert_hostname
        self.ssl_opts = ssl_opts or {}
        deprecated_params = {'keyfile': key_file, 'certfile': cert_file, 'ca_certs': ca_certs}
        for opt, val in deprecated_params.items():
            if val is not None:
                warnings.warn('key_file, cert_file, and ca_certs arguments are deprecated; use ssl_opts argument instead', DeprecationWarning)
                self.ssl_opts.setdefault(opt, val)

        # TODO: This could probably be a little cleaner :)
        if pool_connections:
            if self.type == "https":
                self.transport = TLSConnectionPoolSafeTransport(timeout=timeout)
            else:
                self.transport = TLSConnectionPoolTransport(timeout=timeout)
        else:
            if self.type == "https":
                self.transport = SafeTransport(timeout=timeout, ssl_opts=self.ssl_opts, validate_cert_hostname=self.validate_cert_hostname)
            else:
                self.transport = Transport(timeout=timeout)

        self.extra_headers = extra_headers
        self.id = 0  # Initialize our request ID (gets incremented for every request)

    def _request(self, methodname, params):
        """
        Overriden __request method which introduced the request_cookie parameter
        that allows cookie headers to propagated through JSON-RPC requests.

        :param methodname: Name of method to be called.
        :type methodname: C{str}

        :param params: Parameters list to send to method.
        :type params: C{list}

        :return: The decoded result.

        :raise ResponseError: If the response cannot be parsed or is not proper JSON-RPC 1.0 response format.
        :raise ProtocolError: Re-raises exception if non-200 response received.
        :raise Fault: If the response is an error message from remote application.
        """
        self.id += 1  # Increment our "unique" identifier for every request.

        data = dict(id=self.id, method=methodname, params=params)

        headers = self.extra_headers

        self._prepare_request(data, headers)

        body = json.dumps(data)

        response = self.transport.request(self.host, self.handler, body, headers=headers)

        self._handle_response(response)

        data = response.read()

        try:
            decoded = json.loads(data.decode('utf-8'))
        except Exception as x:
            raise ResponseError("Unable to parse response data as JSON: %s" % x)

        # This special casing for non-compliant systems like DD that sometimes
        # just return NULL from actions and think they're communicating w/ valid
        # JSON-RPC.
        if decoded is None:
            return None

        if not (('result' in decoded) or ('error' in decoded)):
            # Include the decoded result (or part of it) in the error we raise
            r = repr(decoded)
            if len(r) > 256:  # a hard-coded value to keep the exception message to a sane length
                r = r[0:255] + '...'
            raise ResponseError('Malformed JSON-RPC response to %s: %s' % (methodname, r))

        if 'error' in decoded and decoded['error']:
            raise Fault(decoded['error']['code'], decoded['error']['message'])

        if 'result' not in decoded:
            raise ResponseError('Malformed JSON-RPC response: %r' % decoded)

        return decoded['result']

    def _prepare_request(self, data, headers):
        """
        An extension point hook for preparing the request data before it is encoded
        and sent to server.

        This method may modify the body (C{dict}) and headers (C{dict}).  Note
        that some headers (e.g. Content-Length) may be added by the underlying
        libraries (e.g. httplib).

        :param data: The request data (C{dict}) that will be sent to server.
        :type data: C{dict}

        :param headers: Headers that will be sent with the request.  This includes
                        any headers from the extra_headers instance var.
        :type headers: C{dict}
        """
        pass

    def _handle_response(self, response):
        """
        An extension point hook for processing the raw response objects from the server.

        :param response: The HTTP response object.
        :type response: C{httplib.HTTPResponse}
        """
        pass

    def __getattr__(self, name):
        """
        Does the proxy magic: returns a proxy callable that will perform request (when called).

        :return: The callable method object which will perform the request to JSON-RPC server.
        :rtype: L{_Method}
        """
        return self.method_class(self._request, name)

    def __repr__(self):
        return ("<%s for %s%s>" % (self.__class__.__name__, self.host, self.handler))