42428013 websocket patch from commaai/websocket-client.git 0fda5bb7 add jsonrpc 8139b06b add websocket_client git-subtree-dir: pyextra git-subtree-split: 4242801316e12c55e5b7c626331fbefad2e15e0cpull/1/head
							parent
							
								
									9a79df8a8a
								
							
						
					
					
						commit
						342bb13bff
					
				
				 69 changed files with 8796 additions and 0 deletions
			
			
		| @ -0,0 +1,89 @@ | ||||
| Metadata-Version: 1.1 | ||||
| Name: backports.ssl-match-hostname | ||||
| Version: 3.7.0.1 | ||||
| Summary: The ssl.match_hostname() function from Python 3.5 | ||||
| Home-page: http://bitbucket.org/brandon/backports.ssl_match_hostname | ||||
| Author: Toshio Kuratomi | ||||
| Author-email: toshio@fedoraproject.org | ||||
| License: Python Software Foundation License | ||||
| Description:  | ||||
|         The ssl.match_hostname() function from Python 3.7 | ||||
|         ================================================= | ||||
|          | ||||
|         The Secure Sockets Layer is only actually *secure* | ||||
|         if you check the hostname in the certificate returned | ||||
|         by the server to which you are connecting, | ||||
|         and verify that it matches to hostname | ||||
|         that you are trying to reach. | ||||
|          | ||||
|         But the matching logic, defined in `RFC2818`_, | ||||
|         can be a bit tricky to implement on your own. | ||||
|         So the ``ssl`` package in the Standard Library of Python 3.2 | ||||
|         and greater now includes a ``match_hostname()`` function | ||||
|         for performing this check instead of requiring every application | ||||
|         to implement the check separately. | ||||
|          | ||||
|         This backport brings ``match_hostname()`` to users | ||||
|         of earlier versions of Python. | ||||
|         Simply make this distribution a dependency of your package, | ||||
|         and then use it like this:: | ||||
|          | ||||
|             from backports.ssl_match_hostname import match_hostname, CertificateError | ||||
|             [...] | ||||
|             sslsock = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_SSLv23, | ||||
|                                       cert_reqs=ssl.CERT_REQUIRED, ca_certs=...) | ||||
|             try: | ||||
|                 match_hostname(sslsock.getpeercert(), hostname) | ||||
|             except CertificateError, ce: | ||||
|                 ... | ||||
|          | ||||
|         Brandon Craig Rhodes is merely the packager of this distribution; | ||||
|         the actual code inside comes from Python 3.7 with small changes for | ||||
|         portability. | ||||
|          | ||||
|          | ||||
|         Requirements | ||||
|         ------------ | ||||
|          | ||||
|         * If you need to use this on Python versions earlier than 2.6 you will need to | ||||
|           install the `ssl module`_.  From Python 2.6 upwards ``ssl`` is included in | ||||
|           the Python Standard Library so you do not need to install it separately. | ||||
|          | ||||
|         .. _`ssl module`:: https://pypi.python.org/pypi/ssl | ||||
|          | ||||
|         History | ||||
|         ------- | ||||
|          | ||||
|         * This function was introduced in python-3.2 | ||||
|         * It was updated for python-3.4a1 for a CVE  | ||||
|           (backports-ssl_match_hostname-3.4.0.1) | ||||
|         * It was updated from RFC2818 to RFC 6125 compliance in order to fix another | ||||
|           security flaw for python-3.3.3 and python-3.4a5 | ||||
|           (backports-ssl_match_hostname-3.4.0.2) | ||||
|         * It was updated in python-3.5 to handle IPAddresses in ServerAltName fields | ||||
|           (something that backports.ssl_match_hostname will do if you also install the | ||||
|           ipaddress library from pypi). | ||||
|         * It was updated in python-3.7 to handle IPAddresses without the ipaddress library and dropped | ||||
|           support for partial wildcards | ||||
|          | ||||
|         .. _`ipaddress module`:: https://pypi.python.org/pypi/ipaddress | ||||
|          | ||||
|         .. _RFC2818: http://tools.ietf.org/html/rfc2818.html | ||||
|          | ||||
| Platform: UNKNOWN | ||||
| Classifier: Development Status :: 5 - Production/Stable | ||||
| Classifier: License :: OSI Approved :: Python Software Foundation License | ||||
| Classifier: Programming Language :: Python :: 2.4 | ||||
| Classifier: Programming Language :: Python :: 2.5 | ||||
| Classifier: Programming Language :: Python :: 2.6 | ||||
| Classifier: Programming Language :: Python :: 2.7 | ||||
| Classifier: Programming Language :: Python :: 3 | ||||
| Classifier: Programming Language :: Python :: 3.0 | ||||
| Classifier: Programming Language :: Python :: 3.1 | ||||
| Classifier: Programming Language :: Python :: 3.2 | ||||
| Classifier: Programming Language :: Python :: 3.3 | ||||
| Classifier: Programming Language :: Python :: 3.4 | ||||
| Classifier: Programming Language :: Python :: 3.5 | ||||
| Classifier: Programming Language :: Python :: 3.6 | ||||
| Classifier: Programming Language :: Python :: 3.7 | ||||
| Classifier: Topic :: Security :: Cryptography | ||||
| @ -0,0 +1,8 @@ | ||||
| README.txt | ||||
| setup.cfg | ||||
| backports/__init__.py | ||||
| backports.ssl_match_hostname.egg-info/PKG-INFO | ||||
| backports.ssl_match_hostname.egg-info/SOURCES.txt | ||||
| backports.ssl_match_hostname.egg-info/dependency_links.txt | ||||
| backports.ssl_match_hostname.egg-info/top_level.txt | ||||
| backports/ssl_match_hostname/__init__.py | ||||
| @ -0,0 +1 @@ | ||||
| 
 | ||||
| @ -0,0 +1,8 @@ | ||||
| ../backports/__init__.py | ||||
| ../backports/__init__.pyc | ||||
| ../backports/ssl_match_hostname/__init__.py | ||||
| ../backports/ssl_match_hostname/__init__.pyc | ||||
| PKG-INFO | ||||
| SOURCES.txt | ||||
| dependency_links.txt | ||||
| top_level.txt | ||||
| @ -0,0 +1 @@ | ||||
| backports | ||||
| @ -0,0 +1,3 @@ | ||||
| # This is a Python "namespace package" http://www.python.org/dev/peps/pep-0382/ | ||||
| from pkgutil import extend_path | ||||
| __path__ = extend_path(__path__, __name__) | ||||
| @ -0,0 +1,204 @@ | ||||
| """The match_hostname() function from Python 3.7.0, essential when using SSL.""" | ||||
| 
 | ||||
| import sys | ||||
| import socket as _socket | ||||
| 
 | ||||
| try: | ||||
|     # Divergence: Python-3.7+'s _ssl has this exception type but older Pythons do not | ||||
|     from _ssl import SSLCertVerificationError | ||||
|     CertificateError = SSLCertVerificationError | ||||
| except: | ||||
|     class CertificateError(ValueError): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| __version__ = '3.7.0.1' | ||||
| 
 | ||||
| 
 | ||||
| # Divergence: Added to deal with ipaddess as bytes on python2 | ||||
| def _to_text(obj): | ||||
|     if isinstance(obj, str) and sys.version_info < (3,): | ||||
|         obj = unicode(obj, encoding='ascii', errors='strict') | ||||
|     elif sys.version_info >= (3,) and isinstance(obj, bytes): | ||||
|         obj = str(obj, encoding='ascii', errors='strict') | ||||
|     return obj | ||||
| 
 | ||||
| 
 | ||||
| def _to_bytes(obj): | ||||
|     if isinstance(obj, str) and sys.version_info >= (3,): | ||||
|         obj = bytes(obj, encoding='ascii', errors='strict') | ||||
|     elif sys.version_info < (3,) and isinstance(obj, unicode): | ||||
|         obj = obj.encode('ascii', 'strict') | ||||
|     return obj | ||||
| 
 | ||||
| 
 | ||||
| def _dnsname_match(dn, hostname): | ||||
|     """Matching according to RFC 6125, section 6.4.3 | ||||
| 
 | ||||
|     - Hostnames are compared lower case. | ||||
|     - For IDNA, both dn and hostname must be encoded as IDN A-label (ACE). | ||||
|     - Partial wildcards like 'www*.example.org', multiple wildcards, sole | ||||
|       wildcard or wildcards in labels other then the left-most label are not | ||||
|       supported and a CertificateError is raised. | ||||
|     - A wildcard must match at least one character. | ||||
|     """ | ||||
|     if not dn: | ||||
|         return False | ||||
| 
 | ||||
|     wildcards = dn.count('*') | ||||
|     # speed up common case w/o wildcards | ||||
|     if not wildcards: | ||||
|         return dn.lower() == hostname.lower() | ||||
| 
 | ||||
|     if wildcards > 1: | ||||
|         # Divergence .format() to percent formatting for Python < 2.6 | ||||
|         raise CertificateError( | ||||
|             "too many wildcards in certificate DNS name: %s" % repr(dn)) | ||||
| 
 | ||||
|     dn_leftmost, sep, dn_remainder = dn.partition('.') | ||||
| 
 | ||||
|     if '*' in dn_remainder: | ||||
|         # Only match wildcard in leftmost segment. | ||||
|         # Divergence .format() to percent formatting for Python < 2.6 | ||||
|         raise CertificateError( | ||||
|             "wildcard can only be present in the leftmost label: " | ||||
|             "%s." % repr(dn)) | ||||
| 
 | ||||
|     if not sep: | ||||
|         # no right side | ||||
|         # Divergence .format() to percent formatting for Python < 2.6 | ||||
|         raise CertificateError( | ||||
|             "sole wildcard without additional labels are not support: " | ||||
|             "%s." % repr(dn)) | ||||
| 
 | ||||
|     if dn_leftmost != '*': | ||||
|         # no partial wildcard matching | ||||
|         # Divergence .format() to percent formatting for Python < 2.6 | ||||
|         raise CertificateError( | ||||
|             "partial wildcards in leftmost label are not supported: " | ||||
|             "%s." % repr(dn)) | ||||
| 
 | ||||
|     hostname_leftmost, sep, hostname_remainder = hostname.partition('.') | ||||
|     if not hostname_leftmost or not sep: | ||||
|         # wildcard must match at least one char | ||||
|         return False | ||||
|     return dn_remainder.lower() == hostname_remainder.lower() | ||||
| 
 | ||||
| 
 | ||||
| def _inet_paton(ipname): | ||||
|     """Try to convert an IP address to packed binary form | ||||
| 
 | ||||
|     Supports IPv4 addresses on all platforms and IPv6 on platforms with IPv6 | ||||
|     support. | ||||
|     """ | ||||
|     # inet_aton() also accepts strings like '1' | ||||
|     # Divergence: We make sure we have native string type for all python versions | ||||
|     try: | ||||
|         b_ipname = _to_bytes(ipname) | ||||
|     except UnicodeError: | ||||
|         raise ValueError("%s must be an all-ascii string." % repr(ipname)) | ||||
| 
 | ||||
|     # Set ipname in native string format | ||||
|     if sys.version_info < (3,): | ||||
|         n_ipname = b_ipname | ||||
|     else: | ||||
|         n_ipname = ipname | ||||
| 
 | ||||
|     if n_ipname.count('.') == 3: | ||||
|         try: | ||||
|             return _socket.inet_aton(n_ipname) | ||||
|         # Divergence: OSError on late python3.  socket.error earlier. | ||||
|         # Null bytes generate ValueError on python3(we want to raise | ||||
|         # ValueError anyway), TypeError # earlier | ||||
|         except (OSError, _socket.error, TypeError): | ||||
|             pass | ||||
| 
 | ||||
|     try: | ||||
|         return _socket.inet_pton(_socket.AF_INET6, n_ipname) | ||||
|     # Divergence: OSError on late python3.  socket.error earlier. | ||||
|     # Null bytes generate ValueError on python3(we want to raise | ||||
|     # ValueError anyway), TypeError # earlier | ||||
|     except (OSError, _socket.error, TypeError): | ||||
|         # Divergence .format() to percent formatting for Python < 2.6 | ||||
|         raise ValueError("%s is neither an IPv4 nor an IP6 " | ||||
|                          "address." % repr(ipname)) | ||||
|     except AttributeError: | ||||
|         # AF_INET6 not available | ||||
|         pass | ||||
| 
 | ||||
|     # Divergence .format() to percent formatting for Python < 2.6 | ||||
|     raise ValueError("%s is not an IPv4 address." % repr(ipname)) | ||||
| 
 | ||||
| 
 | ||||
| def _ipaddress_match(ipname, host_ip): | ||||
|     """Exact matching of IP addresses. | ||||
| 
 | ||||
|     RFC 6125 explicitly doesn't define an algorithm for this | ||||
|     (section 1.7.2 - "Out of Scope"). | ||||
|     """ | ||||
|     # OpenSSL may add a trailing newline to a subjectAltName's IP address | ||||
|     ip = _inet_paton(ipname.rstrip()) | ||||
|     return ip == host_ip | ||||
| 
 | ||||
| 
 | ||||
| def match_hostname(cert, hostname): | ||||
|     """Verify that *cert* (in decoded format as returned by | ||||
|     SSLSocket.getpeercert()) matches the *hostname*.  RFC 2818 and RFC 6125 | ||||
|     rules are followed. | ||||
| 
 | ||||
|     The function matches IP addresses rather than dNSNames if hostname is a | ||||
|     valid ipaddress string. IPv4 addresses are supported on all platforms. | ||||
|     IPv6 addresses are supported on platforms with IPv6 support (AF_INET6 | ||||
|     and inet_pton). | ||||
| 
 | ||||
|     CertificateError is raised on failure. On success, the function | ||||
|     returns nothing. | ||||
|     """ | ||||
|     if not cert: | ||||
|         raise ValueError("empty or no certificate, match_hostname needs a " | ||||
|                          "SSL socket or SSL context with either " | ||||
|                          "CERT_OPTIONAL or CERT_REQUIRED") | ||||
|     try: | ||||
|         # Divergence: Deal with hostname as bytes | ||||
|         host_ip = _inet_paton(_to_text(hostname)) | ||||
|     except ValueError: | ||||
|         # Not an IP address (common case) | ||||
|         host_ip = None | ||||
|     except UnicodeError: | ||||
|         # Divergence: Deal with hostname as byte strings. | ||||
|         # IP addresses should be all ascii, so we consider it not | ||||
|         # an IP address if this fails | ||||
|         host_ip = None | ||||
|     dnsnames = [] | ||||
|     san = cert.get('subjectAltName', ()) | ||||
|     for key, value in san: | ||||
|         if key == 'DNS': | ||||
|             if host_ip is None and _dnsname_match(value, hostname): | ||||
|                 return | ||||
|             dnsnames.append(value) | ||||
|         elif key == 'IP Address': | ||||
|             if host_ip is not None and _ipaddress_match(value, host_ip): | ||||
|                 return | ||||
|             dnsnames.append(value) | ||||
|     if not dnsnames: | ||||
|         # The subject is only checked when there is no dNSName entry | ||||
|         # in subjectAltName | ||||
|         for sub in cert.get('subject', ()): | ||||
|             for key, value in sub: | ||||
|                 # XXX according to RFC 2818, the most specific Common Name | ||||
|                 # must be used. | ||||
|                 if key == 'commonName': | ||||
|                     if _dnsname_match(value, hostname): | ||||
|                         return | ||||
|                     dnsnames.append(value) | ||||
|     if len(dnsnames) > 1: | ||||
|         raise CertificateError("hostname %r " | ||||
|             "doesn't match either of %s" | ||||
|             % (hostname, ', '.join(map(repr, dnsnames)))) | ||||
|     elif len(dnsnames) == 1: | ||||
|         raise CertificateError("hostname %r " | ||||
|             "doesn't match %r" | ||||
|             % (hostname, dnsnames[0])) | ||||
|     else: | ||||
|         raise CertificateError("no appropriate commonName or " | ||||
|             "subjectAltName fields were found") | ||||
| @ -0,0 +1,201 @@ | ||||
| #!/usr/local/bin/python | ||||
| 
 | ||||
| import argparse | ||||
| import code | ||||
| import sys | ||||
| import threading | ||||
| import time | ||||
| import ssl | ||||
| 
 | ||||
| import six | ||||
| from six.moves.urllib.parse import urlparse | ||||
| 
 | ||||
| import websocket | ||||
| 
 | ||||
| try: | ||||
|     import readline | ||||
| except ImportError: | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| def get_encoding(): | ||||
|     encoding = getattr(sys.stdin, "encoding", "") | ||||
|     if not encoding: | ||||
|         return "utf-8" | ||||
|     else: | ||||
|         return encoding.lower() | ||||
| 
 | ||||
| 
 | ||||
| OPCODE_DATA = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY) | ||||
| ENCODING = get_encoding() | ||||
| 
 | ||||
| 
 | ||||
| class VAction(argparse.Action): | ||||
| 
 | ||||
|     def __call__(self, parser, args, values, option_string=None): | ||||
|         if values is None: | ||||
|             values = "1" | ||||
|         try: | ||||
|             values = int(values) | ||||
|         except ValueError: | ||||
|             values = values.count("v") + 1 | ||||
|         setattr(args, self.dest, values) | ||||
| 
 | ||||
| 
 | ||||
| def parse_args(): | ||||
|     parser = argparse.ArgumentParser(description="WebSocket Simple Dump Tool") | ||||
|     parser.add_argument("url", metavar="ws_url", | ||||
|                         help="websocket url. ex. ws://echo.websocket.org/") | ||||
|     parser.add_argument("-p", "--proxy", | ||||
|                         help="proxy url. ex. http://127.0.0.1:8080") | ||||
|     parser.add_argument("-v", "--verbose", default=0, nargs='?', action=VAction, | ||||
|                         dest="verbose", | ||||
|                         help="set verbose mode. If set to 1, show opcode. " | ||||
|                         "If set to 2, enable to trace  websocket module") | ||||
|     parser.add_argument("-n", "--nocert", action='store_true', | ||||
|                         help="Ignore invalid SSL cert") | ||||
|     parser.add_argument("-r", "--raw", action="store_true", | ||||
|                         help="raw output") | ||||
|     parser.add_argument("-s", "--subprotocols", nargs='*', | ||||
|                         help="Set subprotocols") | ||||
|     parser.add_argument("-o", "--origin", | ||||
|                         help="Set origin") | ||||
|     parser.add_argument("--eof-wait", default=0, type=int, | ||||
|                         help="wait time(second) after 'EOF' received.") | ||||
|     parser.add_argument("-t", "--text", | ||||
|                         help="Send initial text") | ||||
|     parser.add_argument("--timings", action="store_true", | ||||
|                         help="Print timings in seconds") | ||||
|     parser.add_argument("--headers", | ||||
|                         help="Set custom headers. Use ',' as separator") | ||||
| 
 | ||||
|     return parser.parse_args() | ||||
| 
 | ||||
| 
 | ||||
| class RawInput: | ||||
| 
 | ||||
|     def raw_input(self, prompt): | ||||
|         if six.PY3: | ||||
|             line = input(prompt) | ||||
|         else: | ||||
|             line = raw_input(prompt) | ||||
| 
 | ||||
|         if ENCODING and ENCODING != "utf-8" and not isinstance(line, six.text_type): | ||||
|             line = line.decode(ENCODING).encode("utf-8") | ||||
|         elif isinstance(line, six.text_type): | ||||
|             line = line.encode("utf-8") | ||||
| 
 | ||||
|         return line | ||||
| 
 | ||||
| 
 | ||||
| class InteractiveConsole(RawInput, code.InteractiveConsole): | ||||
| 
 | ||||
|     def write(self, data): | ||||
|         sys.stdout.write("\033[2K\033[E") | ||||
|         # sys.stdout.write("\n") | ||||
|         sys.stdout.write("\033[34m< " + data + "\033[39m") | ||||
|         sys.stdout.write("\n> ") | ||||
|         sys.stdout.flush() | ||||
| 
 | ||||
|     def read(self): | ||||
|         return self.raw_input("> ") | ||||
| 
 | ||||
| 
 | ||||
| class NonInteractive(RawInput): | ||||
| 
 | ||||
|     def write(self, data): | ||||
|         sys.stdout.write(data) | ||||
|         sys.stdout.write("\n") | ||||
|         sys.stdout.flush() | ||||
| 
 | ||||
|     def read(self): | ||||
|         return self.raw_input("") | ||||
| 
 | ||||
| 
 | ||||
| def main(): | ||||
|     start_time = time.time() | ||||
|     args = parse_args() | ||||
|     if args.verbose > 1: | ||||
|         websocket.enableTrace(True) | ||||
|     options = {} | ||||
|     if args.proxy: | ||||
|         p = urlparse(args.proxy) | ||||
|         options["http_proxy_host"] = p.hostname | ||||
|         options["http_proxy_port"] = p.port | ||||
|     if args.origin: | ||||
|         options["origin"] = args.origin | ||||
|     if args.subprotocols: | ||||
|         options["subprotocols"] = args.subprotocols | ||||
|     opts = {} | ||||
|     if args.nocert: | ||||
|         opts = {"cert_reqs": ssl.CERT_NONE, "check_hostname": False} | ||||
|     if args.headers: | ||||
|         options['header'] = map(str.strip, args.headers.split(',')) | ||||
|     ws = websocket.create_connection(args.url, sslopt=opts, **options) | ||||
|     if args.raw: | ||||
|         console = NonInteractive() | ||||
|     else: | ||||
|         console = InteractiveConsole() | ||||
|         print("Press Ctrl+C to quit") | ||||
| 
 | ||||
|     def recv(): | ||||
|         try: | ||||
|             frame = ws.recv_frame() | ||||
|         except websocket.WebSocketException: | ||||
|             return websocket.ABNF.OPCODE_CLOSE, None | ||||
|         if not frame: | ||||
|             raise websocket.WebSocketException("Not a valid frame %s" % frame) | ||||
|         elif frame.opcode in OPCODE_DATA: | ||||
|             return frame.opcode, frame.data | ||||
|         elif frame.opcode == websocket.ABNF.OPCODE_CLOSE: | ||||
|             ws.send_close() | ||||
|             return frame.opcode, None | ||||
|         elif frame.opcode == websocket.ABNF.OPCODE_PING: | ||||
|             ws.pong(frame.data) | ||||
|             return frame.opcode, frame.data | ||||
| 
 | ||||
|         return frame.opcode, frame.data | ||||
| 
 | ||||
|     def recv_ws(): | ||||
|         while True: | ||||
|             opcode, data = recv() | ||||
|             msg = None | ||||
|             if six.PY3 and opcode == websocket.ABNF.OPCODE_TEXT and isinstance(data, bytes): | ||||
|                 data = str(data, "utf-8") | ||||
|             if not args.verbose and opcode in OPCODE_DATA: | ||||
|                 msg = data | ||||
|             elif args.verbose: | ||||
|                 msg = "%s: %s" % (websocket.ABNF.OPCODE_MAP.get(opcode), data) | ||||
| 
 | ||||
|             if msg is not None: | ||||
|                 if args.timings: | ||||
|                     console.write(str(time.time() - start_time) + ": " + msg) | ||||
|                 else: | ||||
|                     console.write(msg) | ||||
| 
 | ||||
|             if opcode == websocket.ABNF.OPCODE_CLOSE: | ||||
|                 break | ||||
| 
 | ||||
|     thread = threading.Thread(target=recv_ws) | ||||
|     thread.daemon = True | ||||
|     thread.start() | ||||
| 
 | ||||
|     if args.text: | ||||
|         ws.send(args.text) | ||||
| 
 | ||||
|     while True: | ||||
|         try: | ||||
|             message = console.read() | ||||
|             ws.send(message) | ||||
|         except KeyboardInterrupt: | ||||
|             return | ||||
|         except EOFError: | ||||
|             time.sleep(args.eof_wait) | ||||
|             return | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     try: | ||||
|         main() | ||||
|     except Exception as e: | ||||
|         print(e) | ||||
| @ -0,0 +1,187 @@ | ||||
| Metadata-Version: 1.1 | ||||
| Name: json-rpc | ||||
| Version: 1.12.1 | ||||
| Summary: JSON-RPC transport implementation | ||||
| Home-page: https://github.com/pavlov99/json-rpc | ||||
| Author: Kirill Pavlov | ||||
| Author-email: k@p99.io | ||||
| License: MIT | ||||
| Description: json-rpc | ||||
|         ======== | ||||
|          | ||||
|         .. image:: https://circleci.com/gh/pavlov99/json-rpc/tree/master.svg?style=svg | ||||
|             :target: https://circleci.com/gh/pavlov99/json-rpc/tree/master | ||||
|             :alt: Build Status | ||||
|          | ||||
|         .. image:: https://codecov.io/gh/pavlov99/json-rpc/branch/master/graph/badge.svg | ||||
|             :target: https://codecov.io/gh/pavlov99/json-rpc | ||||
|             :alt: Coverage Status | ||||
|          | ||||
|         .. image:: https://readthedocs.org/projects/json-rpc/badge/?version=latest | ||||
|             :target: http://json-rpc.readthedocs.io/en/latest/?badge=latest | ||||
|          | ||||
|         .. image:: https://img.shields.io/pypi/v/json-rpc.svg | ||||
|             :target: https://pypi.org/project/json-rpc/ | ||||
|             :alt: Latest PyPI version | ||||
|          | ||||
|         .. image:: https://img.shields.io/pypi/pyversions/json-rpc.svg | ||||
|             :target: https://pypi.org/project/json-rpc/ | ||||
|             :alt: Supported Python versions | ||||
|          | ||||
|         .. image:: https://badges.gitter.im/pavlov99/json-rpc.svg | ||||
|             :target: https://gitter.im/pavlov99/json-rpc | ||||
|             :alt: Gitter | ||||
|          | ||||
|          | ||||
|         .. image:: https://opencollective.com/json-rpc/tiers/backer/badge.svg?label=backer&color=brightgreen | ||||
|             :target: https://opencollective.com/json-rpc | ||||
|             :alt: Bakers | ||||
|          | ||||
|         .. image:: https://opencollective.com/json-rpc/tiers/backer/badge.svg?label=sponsor&color=brightgreen | ||||
|             :target: https://opencollective.com/json-rpc | ||||
|             :alt: Sponsors | ||||
|          | ||||
|         `JSON-RPC2.0 <http://www.jsonrpc.org/specification>`_ and `JSON-RPC1.0 <http://json-rpc.org/wiki/specification>`_ transport specification implementation. | ||||
|         Supports Python 2.6+, Python 3.3+, PyPy. Has optional Django and Flask support. 200+ tests. | ||||
|          | ||||
|         Features | ||||
|         -------- | ||||
|          | ||||
|         This implementation does not have any transport functionality realization, only protocol. | ||||
|         Any client or server implementation is easy based on current code, but requires transport libraries, such as requests, gevent or zmq, see `examples <https://github.com/pavlov99/json-rpc/tree/master/examples>`_. | ||||
|          | ||||
|         - Vanilla Python, no dependencies. | ||||
|         - 200+ tests for multiple edge cases. | ||||
|         - Optional backend support for Django, Flask. | ||||
|         - json-rpc 1.1 and 2.0 support. | ||||
|          | ||||
|         Install | ||||
|         ------- | ||||
|          | ||||
|         .. code-block:: python | ||||
|          | ||||
|             pip install json-rpc | ||||
|          | ||||
|         Tests | ||||
|         ----- | ||||
|          | ||||
|         Quickstart | ||||
|         ^^^^^^^^^^ | ||||
|         This is an essential part of the library as there are a lot of edge cases in JSON-RPC standard. To manage a variety of supported python versions as well as optional backends json-rpc uses `tox`: | ||||
|          | ||||
|         .. code-block:: bash | ||||
|          | ||||
|             tox | ||||
|          | ||||
|         .. TIP:: | ||||
|            During local development use your python version with tox runner. For example, if your are using Python 3.6 run `tox -e py36`. It is easier to develop functionality for specific version first and then expands it to all of the supported versions. | ||||
|          | ||||
|         Continuous integration | ||||
|         ^^^^^^^^^^^^^^^^^^^^^^ | ||||
|         This project uses `CircleCI <https://circleci.com/>`_ for continuous integration. All of the python supported versions are managed via `tox.ini` and `.circleci/config.yml` files. Master branch test status is displayed on the badge in the beginning of this document. | ||||
|          | ||||
|         Test matrix | ||||
|         ^^^^^^^^^^^ | ||||
|         json-rpc supports multiple python versions: 2.6+, 3.3+, pypy. This introduces difficulties with testing libraries and optional dependencies management. For example, python before version 3.3 does not support `mock` and there is a limited support for `unittest2`. Every dependency translates into *if-then* blocks in the source code and adds complexity to it. Hence, while cross-python support is a core feature of this library, cross-Django or cross-Flask support is limited. In general, json-rpc uses latest stable release which supports current python version. For example, python 2.6 is compatible with Django 1.6 and not compatible with any future versions. | ||||
|          | ||||
|         Below is a testing matrix: | ||||
|          | ||||
|         +--------+-------+-----------+--------+--------+ | ||||
|         | Python | mock  | unittest  | Django | Flask  | | ||||
|         +========+=======+===========+========+========+ | ||||
|         | 2.6    | 2.0.0 | unittest2 | 1.6    | 0.12.2 | | ||||
|         +--------+-------+-----------+--------+--------+ | ||||
|         | 2.7    | 2.0.0 |           | 1.11   | 0.12.2 | | ||||
|         +--------+-------+-----------+--------+--------+ | ||||
|         | 3.3    |       |           | 1.11   | 0.12.2 | | ||||
|         +--------+-------+-----------+--------+--------+ | ||||
|         | 3.4    |       |           | 1.11   | 0.12.2 | | ||||
|         +--------+-------+-----------+--------+--------+ | ||||
|         | 3.5    |       |           | 1.11   | 0.12.2 | | ||||
|         +--------+-------+-----------+--------+--------+ | ||||
|         | 3.6    |       |           | 1.11   | 0.12.2 | | ||||
|         +--------+-------+-----------+--------+--------+ | ||||
|         | pypy   | 2.0.0 |           | 1.11   | 0.12.2 | | ||||
|         +--------+-------+-----------+--------+--------+ | ||||
|         | pypy3  |       |           | 1.11   | 0.12.2 | | ||||
|         +--------+-------+-----------+--------+--------+ | ||||
|          | ||||
|         Quickstart | ||||
|         ---------- | ||||
|         Server (uses `Werkzeug <http://werkzeug.pocoo.org/>`_) | ||||
|          | ||||
|         .. code-block:: python | ||||
|          | ||||
|             from werkzeug.wrappers import Request, Response | ||||
|             from werkzeug.serving import run_simple | ||||
|          | ||||
|             from jsonrpc import JSONRPCResponseManager, dispatcher | ||||
|          | ||||
|          | ||||
|             @dispatcher.add_method | ||||
|             def foobar(**kwargs): | ||||
|                 return kwargs["foo"] + kwargs["bar"] | ||||
|          | ||||
|          | ||||
|             @Request.application | ||||
|             def application(request): | ||||
|                 # Dispatcher is dictionary {<method_name>: callable} | ||||
|                 dispatcher["echo"] = lambda s: s | ||||
|                 dispatcher["add"] = lambda a, b: a + b | ||||
|          | ||||
|                 response = JSONRPCResponseManager.handle( | ||||
|                     request.data, dispatcher) | ||||
|                 return Response(response.json, mimetype='application/json') | ||||
|          | ||||
|          | ||||
|             if __name__ == '__main__': | ||||
|                 run_simple('localhost', 4000, application) | ||||
|          | ||||
|         Client (uses `requests <http://www.python-requests.org/en/latest/>`_) | ||||
|          | ||||
|         .. code-block:: python | ||||
|          | ||||
|             import requests | ||||
|             import json | ||||
|          | ||||
|          | ||||
|             def main(): | ||||
|                 url = "http://localhost:4000/jsonrpc" | ||||
|                 headers = {'content-type': 'application/json'} | ||||
|          | ||||
|                 # Example echo method | ||||
|                 payload = { | ||||
|                     "method": "echo", | ||||
|                     "params": ["echome!"], | ||||
|                     "jsonrpc": "2.0", | ||||
|                     "id": 0, | ||||
|                 } | ||||
|                 response = requests.post( | ||||
|                     url, data=json.dumps(payload), headers=headers).json() | ||||
|          | ||||
|                 assert response["result"] == "echome!" | ||||
|                 assert response["jsonrpc"] | ||||
|                 assert response["id"] == 0 | ||||
|          | ||||
|             if __name__ == "__main__": | ||||
|                 main() | ||||
|          | ||||
|         Competitors | ||||
|         ----------- | ||||
|         There are `several libraries <http://en.wikipedia.org/wiki/JSON-RPC#Implementations>`_ implementing JSON-RPC protocol. List below represents python libraries, none of the supports python3. tinyrpc looks better than others. | ||||
|          | ||||
| Platform: UNKNOWN | ||||
| Classifier: Development Status :: 5 - Production/Stable | ||||
| Classifier: Environment :: Console | ||||
| Classifier: License :: OSI Approved :: MIT License | ||||
| Classifier: Natural Language :: English | ||||
| Classifier: Operating System :: OS Independent | ||||
| Classifier: Programming Language :: Python :: 2.6 | ||||
| Classifier: Programming Language :: Python :: 2.7 | ||||
| Classifier: Programming Language :: Python :: 3.3 | ||||
| Classifier: Programming Language :: Python :: 3.4 | ||||
| Classifier: Programming Language :: Python :: 3.5 | ||||
| Classifier: Programming Language :: Python :: 3.6 | ||||
| Classifier: Programming Language :: Python :: 3.7 | ||||
| Classifier: Programming Language :: Python :: Implementation :: PyPy | ||||
| Classifier: Topic :: Software Development :: Libraries :: Python Modules | ||||
| @ -0,0 +1,42 @@ | ||||
| LICENSE.txt | ||||
| MANIFEST.in | ||||
| README.rst | ||||
| get-pip.py | ||||
| setup.cfg | ||||
| setup.py | ||||
| json_rpc.egg-info/PKG-INFO | ||||
| json_rpc.egg-info/SOURCES.txt | ||||
| json_rpc.egg-info/dependency_links.txt | ||||
| json_rpc.egg-info/top_level.txt | ||||
| jsonrpc/__init__.py | ||||
| jsonrpc/base.py | ||||
| jsonrpc/dispatcher.py | ||||
| jsonrpc/exceptions.py | ||||
| jsonrpc/jsonrpc.py | ||||
| jsonrpc/jsonrpc1.py | ||||
| jsonrpc/jsonrpc2.py | ||||
| jsonrpc/manager.py | ||||
| jsonrpc/six.py | ||||
| jsonrpc/utils.py | ||||
| jsonrpc/backend/__init__.py | ||||
| jsonrpc/backend/django.py | ||||
| jsonrpc/backend/flask.py | ||||
| jsonrpc/tests/__init__.py | ||||
| jsonrpc/tests/py35_utils.py | ||||
| jsonrpc/tests/test_base.py | ||||
| jsonrpc/tests/test_bug29.py | ||||
| jsonrpc/tests/test_dispatcher.py | ||||
| jsonrpc/tests/test_examples20.py | ||||
| jsonrpc/tests/test_jsonrpc.py | ||||
| jsonrpc/tests/test_jsonrpc1.py | ||||
| jsonrpc/tests/test_jsonrpc2.py | ||||
| jsonrpc/tests/test_jsonrpc_errors.py | ||||
| jsonrpc/tests/test_manager.py | ||||
| jsonrpc/tests/test_pep3107.py | ||||
| jsonrpc/tests/test_utils.py | ||||
| jsonrpc/tests/test_backend_django/__init__.py | ||||
| jsonrpc/tests/test_backend_django/settings.py | ||||
| jsonrpc/tests/test_backend_django/tests.py | ||||
| jsonrpc/tests/test_backend_django/urls.py | ||||
| jsonrpc/tests/test_backend_flask/__init__.py | ||||
| jsonrpc/tests/test_backend_flask/tests.py | ||||
| @ -0,0 +1 @@ | ||||
| 
 | ||||
| @ -0,0 +1,68 @@ | ||||
| ../jsonrpc/__init__.py | ||||
| ../jsonrpc/__init__.pyc | ||||
| ../jsonrpc/backend/__init__.py | ||||
| ../jsonrpc/backend/__init__.pyc | ||||
| ../jsonrpc/backend/django.py | ||||
| ../jsonrpc/backend/django.pyc | ||||
| ../jsonrpc/backend/flask.py | ||||
| ../jsonrpc/backend/flask.pyc | ||||
| ../jsonrpc/base.py | ||||
| ../jsonrpc/base.pyc | ||||
| ../jsonrpc/dispatcher.py | ||||
| ../jsonrpc/dispatcher.pyc | ||||
| ../jsonrpc/exceptions.py | ||||
| ../jsonrpc/exceptions.pyc | ||||
| ../jsonrpc/jsonrpc.py | ||||
| ../jsonrpc/jsonrpc.pyc | ||||
| ../jsonrpc/jsonrpc1.py | ||||
| ../jsonrpc/jsonrpc1.pyc | ||||
| ../jsonrpc/jsonrpc2.py | ||||
| ../jsonrpc/jsonrpc2.pyc | ||||
| ../jsonrpc/manager.py | ||||
| ../jsonrpc/manager.pyc | ||||
| ../jsonrpc/six.py | ||||
| ../jsonrpc/six.pyc | ||||
| ../jsonrpc/tests/__init__.py | ||||
| ../jsonrpc/tests/__init__.pyc | ||||
| ../jsonrpc/tests/py35_utils.py | ||||
| ../jsonrpc/tests/py35_utils.pyc | ||||
| ../jsonrpc/tests/test_backend_django/__init__.py | ||||
| ../jsonrpc/tests/test_backend_django/__init__.pyc | ||||
| ../jsonrpc/tests/test_backend_django/settings.py | ||||
| ../jsonrpc/tests/test_backend_django/settings.pyc | ||||
| ../jsonrpc/tests/test_backend_django/tests.py | ||||
| ../jsonrpc/tests/test_backend_django/tests.pyc | ||||
| ../jsonrpc/tests/test_backend_django/urls.py | ||||
| ../jsonrpc/tests/test_backend_django/urls.pyc | ||||
| ../jsonrpc/tests/test_backend_flask/__init__.py | ||||
| ../jsonrpc/tests/test_backend_flask/__init__.pyc | ||||
| ../jsonrpc/tests/test_backend_flask/tests.py | ||||
| ../jsonrpc/tests/test_backend_flask/tests.pyc | ||||
| ../jsonrpc/tests/test_base.py | ||||
| ../jsonrpc/tests/test_base.pyc | ||||
| ../jsonrpc/tests/test_bug29.py | ||||
| ../jsonrpc/tests/test_bug29.pyc | ||||
| ../jsonrpc/tests/test_dispatcher.py | ||||
| ../jsonrpc/tests/test_dispatcher.pyc | ||||
| ../jsonrpc/tests/test_examples20.py | ||||
| ../jsonrpc/tests/test_examples20.pyc | ||||
| ../jsonrpc/tests/test_jsonrpc.py | ||||
| ../jsonrpc/tests/test_jsonrpc.pyc | ||||
| ../jsonrpc/tests/test_jsonrpc1.py | ||||
| ../jsonrpc/tests/test_jsonrpc1.pyc | ||||
| ../jsonrpc/tests/test_jsonrpc2.py | ||||
| ../jsonrpc/tests/test_jsonrpc2.pyc | ||||
| ../jsonrpc/tests/test_jsonrpc_errors.py | ||||
| ../jsonrpc/tests/test_jsonrpc_errors.pyc | ||||
| ../jsonrpc/tests/test_manager.py | ||||
| ../jsonrpc/tests/test_manager.pyc | ||||
| ../jsonrpc/tests/test_pep3107.py | ||||
| ../jsonrpc/tests/test_pep3107.pyc | ||||
| ../jsonrpc/tests/test_utils.py | ||||
| ../jsonrpc/tests/test_utils.pyc | ||||
| ../jsonrpc/utils.py | ||||
| ../jsonrpc/utils.pyc | ||||
| PKG-INFO | ||||
| SOURCES.txt | ||||
| dependency_links.txt | ||||
| top_level.txt | ||||
| @ -0,0 +1 @@ | ||||
| jsonrpc | ||||
| @ -0,0 +1,11 @@ | ||||
| from .manager import JSONRPCResponseManager | ||||
| from .dispatcher import Dispatcher | ||||
| 
 | ||||
| __version = (1, 12, 1) | ||||
| 
 | ||||
| __version__ = version = '.'.join(map(str, __version)) | ||||
| __project__ = PROJECT = __name__ | ||||
| 
 | ||||
| dispatcher = Dispatcher() | ||||
| 
 | ||||
| # lint_ignore=W0611,W0401 | ||||
| @ -0,0 +1,89 @@ | ||||
| from __future__ import absolute_import | ||||
| 
 | ||||
| from django.views.decorators.csrf import csrf_exempt | ||||
| from django.conf.urls import url | ||||
| from django.conf import settings | ||||
| from django.http import HttpResponse, HttpResponseNotAllowed | ||||
| import copy | ||||
| import json | ||||
| import logging | ||||
| import time | ||||
| 
 | ||||
| from ..exceptions import JSONRPCInvalidRequestException | ||||
| from ..jsonrpc import JSONRPCRequest | ||||
| from ..manager import JSONRPCResponseManager | ||||
| from ..utils import DatetimeDecimalEncoder | ||||
| from ..dispatcher import Dispatcher | ||||
| 
 | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| def response_serialize(obj): | ||||
|     """ Serializes response's data object to JSON. """ | ||||
|     return json.dumps(obj, cls=DatetimeDecimalEncoder) | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCAPI(object): | ||||
|     def __init__(self, dispatcher=None): | ||||
|         self.dispatcher = dispatcher if dispatcher is not None \ | ||||
|             else Dispatcher() | ||||
| 
 | ||||
|     @property | ||||
|     def urls(self): | ||||
|         urls = [ | ||||
|             url(r'^$', self.jsonrpc, name='endpoint'), | ||||
|         ] | ||||
| 
 | ||||
|         if getattr(settings, 'JSONRPC_MAP_VIEW_ENABLED', settings.DEBUG): | ||||
|             urls.append( | ||||
|                 url(r'^map$', self.jsonrpc_map, name='map') | ||||
|             ) | ||||
| 
 | ||||
|         return urls | ||||
| 
 | ||||
|     @csrf_exempt | ||||
|     def jsonrpc(self, request): | ||||
|         """ JSON-RPC 2.0 handler.""" | ||||
|         if request.method != "POST": | ||||
|             return HttpResponseNotAllowed(["POST"]) | ||||
| 
 | ||||
|         request_str = request.body.decode('utf8') | ||||
|         try: | ||||
|             jsonrpc_request = JSONRPCRequest.from_json(request_str) | ||||
|         except (TypeError, ValueError, JSONRPCInvalidRequestException): | ||||
|             response = JSONRPCResponseManager.handle( | ||||
|                 request_str, self.dispatcher) | ||||
|         else: | ||||
|             jsonrpc_request.params = jsonrpc_request.params or {} | ||||
|             jsonrpc_request_params = copy.copy(jsonrpc_request.params) | ||||
|             if isinstance(jsonrpc_request.params, dict): | ||||
|                 jsonrpc_request.params.update(request=request) | ||||
| 
 | ||||
|             t1 = time.time() | ||||
|             response = JSONRPCResponseManager.handle_request( | ||||
|                 jsonrpc_request, self.dispatcher) | ||||
|             t2 = time.time() | ||||
|             logger.info('{0}({1}) {2:.2f} sec'.format( | ||||
|                 jsonrpc_request.method, jsonrpc_request_params, t2 - t1)) | ||||
| 
 | ||||
|         if response: | ||||
|             response.serialize = response_serialize | ||||
|             response = response.json | ||||
| 
 | ||||
|         return HttpResponse(response, content_type="application/json") | ||||
| 
 | ||||
|     def jsonrpc_map(self, request): | ||||
|         """ Map of json-rpc available calls. | ||||
| 
 | ||||
|         :return str: | ||||
| 
 | ||||
|         """ | ||||
|         result = "<h1>JSON-RPC map</h1><pre>{0}</pre>".format("\n\n".join([ | ||||
|             "{0}: {1}".format(fname, f.__doc__) | ||||
|             for fname, f in self.dispatcher.items() | ||||
|         ])) | ||||
|         return HttpResponse(result) | ||||
| 
 | ||||
| 
 | ||||
| api = JSONRPCAPI() | ||||
| @ -0,0 +1,85 @@ | ||||
| from __future__ import absolute_import | ||||
| 
 | ||||
| import copy | ||||
| import json | ||||
| import logging | ||||
| import time | ||||
| from uuid import uuid4 | ||||
| 
 | ||||
| from flask import Blueprint, request, Response | ||||
| 
 | ||||
| from ..exceptions import JSONRPCInvalidRequestException | ||||
| from ..jsonrpc import JSONRPCRequest | ||||
| from ..manager import JSONRPCResponseManager | ||||
| from ..utils import DatetimeDecimalEncoder | ||||
| from ..dispatcher import Dispatcher | ||||
| 
 | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCAPI(object): | ||||
|     def __init__(self, dispatcher=None, check_content_type=True): | ||||
|         """ | ||||
| 
 | ||||
|         :param dispatcher: methods dispatcher | ||||
|         :param check_content_type: if True - content-type must be | ||||
|             "application/json" | ||||
|         :return: | ||||
| 
 | ||||
|         """ | ||||
|         self.dispatcher = dispatcher if dispatcher is not None \ | ||||
|             else Dispatcher() | ||||
|         self.check_content_type = check_content_type | ||||
| 
 | ||||
|     def as_blueprint(self, name=None): | ||||
|         blueprint = Blueprint(name if name else str(uuid4()), __name__) | ||||
|         blueprint.add_url_rule( | ||||
|             '/', view_func=self.jsonrpc, methods=['POST']) | ||||
|         blueprint.add_url_rule( | ||||
|             '/map', view_func=self.jsonrpc_map, methods=['GET']) | ||||
|         return blueprint | ||||
| 
 | ||||
|     def as_view(self): | ||||
|         return self.jsonrpc | ||||
| 
 | ||||
|     def jsonrpc(self): | ||||
|         request_str = self._get_request_str() | ||||
|         try: | ||||
|             jsonrpc_request = JSONRPCRequest.from_json(request_str) | ||||
|         except (TypeError, ValueError, JSONRPCInvalidRequestException): | ||||
|             response = JSONRPCResponseManager.handle( | ||||
|                 request_str, self.dispatcher) | ||||
|         else: | ||||
|             response = JSONRPCResponseManager.handle_request( | ||||
|                 jsonrpc_request, self.dispatcher) | ||||
| 
 | ||||
|         if response: | ||||
|             response.serialize = self._serialize | ||||
|             response = response.json | ||||
| 
 | ||||
|         return Response(response, content_type="application/json") | ||||
| 
 | ||||
|     def jsonrpc_map(self): | ||||
|         """ Map of json-rpc available calls. | ||||
| 
 | ||||
|         :return str: | ||||
| 
 | ||||
|         """ | ||||
|         result = "<h1>JSON-RPC map</h1><pre>{0}</pre>".format("\n\n".join([ | ||||
|             "{0}: {1}".format(fname, f.__doc__) | ||||
|             for fname, f in self.dispatcher.items() | ||||
|         ])) | ||||
|         return Response(result) | ||||
| 
 | ||||
|     def _get_request_str(self): | ||||
|         if self.check_content_type or request.data: | ||||
|             return request.data | ||||
|         return list(request.form.keys())[0] | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _serialize(s): | ||||
|         return json.dumps(s, cls=DatetimeDecimalEncoder) | ||||
| 
 | ||||
| 
 | ||||
| api = JSONRPCAPI() | ||||
| @ -0,0 +1,87 @@ | ||||
| from .utils import JSONSerializable | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCBaseRequest(JSONSerializable): | ||||
| 
 | ||||
|     """ Base class for JSON-RPC 1.0 and JSON-RPC 2.0 requests.""" | ||||
| 
 | ||||
|     def __init__(self, method=None, params=None, _id=None, | ||||
|                  is_notification=None): | ||||
|         self.data = dict() | ||||
|         self.method = method | ||||
|         self.params = params | ||||
|         self._id = _id | ||||
|         self.is_notification = is_notification | ||||
| 
 | ||||
|     @property | ||||
|     def data(self): | ||||
|         return self._data | ||||
| 
 | ||||
|     @data.setter | ||||
|     def data(self, value): | ||||
|         if not isinstance(value, dict): | ||||
|             raise ValueError("data should be dict") | ||||
| 
 | ||||
|         self._data = value | ||||
| 
 | ||||
|     @property | ||||
|     def args(self): | ||||
|         """ Method position arguments. | ||||
| 
 | ||||
|         :return tuple args: method position arguments. | ||||
| 
 | ||||
|         """ | ||||
|         return tuple(self.params) if isinstance(self.params, list) else () | ||||
| 
 | ||||
|     @property | ||||
|     def kwargs(self): | ||||
|         """ Method named arguments. | ||||
| 
 | ||||
|         :return dict kwargs: method named arguments. | ||||
| 
 | ||||
|         """ | ||||
|         return self.params if isinstance(self.params, dict) else {} | ||||
| 
 | ||||
|     @property | ||||
|     def json(self): | ||||
|         return self.serialize(self.data) | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCBaseResponse(JSONSerializable): | ||||
| 
 | ||||
|     """ Base class for JSON-RPC 1.0 and JSON-RPC 2.0 responses.""" | ||||
| 
 | ||||
|     def __init__(self, **kwargs): | ||||
|         self.data = dict() | ||||
| 
 | ||||
|         try: | ||||
|             self.result = kwargs['result'] | ||||
|         except KeyError: | ||||
|             pass | ||||
| 
 | ||||
|         try: | ||||
|             self.error = kwargs['error'] | ||||
|         except KeyError: | ||||
|             pass | ||||
| 
 | ||||
|         self._id = kwargs.get('_id') | ||||
| 
 | ||||
|         if 'result' not in kwargs and 'error' not in kwargs: | ||||
|             raise ValueError("Either result or error should be used") | ||||
| 
 | ||||
|         self.request = None  # type: JSONRPCBaseRequest | ||||
| 
 | ||||
|     @property | ||||
|     def data(self): | ||||
|         return self._data | ||||
| 
 | ||||
|     @data.setter | ||||
|     def data(self, value): | ||||
|         if not isinstance(value, dict): | ||||
|             raise ValueError("data should be dict") | ||||
| 
 | ||||
|         self._data = value | ||||
| 
 | ||||
|     @property | ||||
|     def json(self): | ||||
|         return self.serialize(self.data) | ||||
| @ -0,0 +1,132 @@ | ||||
| """ Dispatcher is used to add methods (functions) to the server. | ||||
| 
 | ||||
| For usage examples see :meth:`Dispatcher.add_method` | ||||
| 
 | ||||
| """ | ||||
| import functools | ||||
| import collections | ||||
| 
 | ||||
| 
 | ||||
| class Dispatcher(collections.MutableMapping): | ||||
| 
 | ||||
|     """ Dictionary like object which maps method_name to method.""" | ||||
| 
 | ||||
|     def __init__(self, prototype=None): | ||||
|         """ Build method dispatcher. | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         prototype : object or dict, optional | ||||
|             Initial method mapping. | ||||
| 
 | ||||
|         Examples | ||||
|         -------- | ||||
| 
 | ||||
|         Init object with method dictionary. | ||||
| 
 | ||||
|         >>> Dispatcher({"sum": lambda a, b: a + b}) | ||||
|         None | ||||
| 
 | ||||
|         """ | ||||
|         self.method_map = dict() | ||||
| 
 | ||||
|         if prototype is not None: | ||||
|             self.build_method_map(prototype) | ||||
| 
 | ||||
|     def __getitem__(self, key): | ||||
|         return self.method_map[key] | ||||
| 
 | ||||
|     def __setitem__(self, key, value): | ||||
|         self.method_map[key] = value | ||||
| 
 | ||||
|     def __delitem__(self, key): | ||||
|         del self.method_map[key] | ||||
| 
 | ||||
|     def __len__(self): | ||||
|         return len(self.method_map) | ||||
| 
 | ||||
|     def __iter__(self): | ||||
|         return iter(self.method_map) | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return repr(self.method_map) | ||||
| 
 | ||||
|     def add_class(self, cls): | ||||
|         prefix = cls.__name__.lower() + '.' | ||||
|         self.build_method_map(cls(), prefix) | ||||
| 
 | ||||
|     def add_object(self, obj): | ||||
|         prefix = obj.__class__.__name__.lower() + '.' | ||||
|         self.build_method_map(obj, prefix) | ||||
| 
 | ||||
|     def add_dict(self, dict, prefix=''): | ||||
|         if prefix: | ||||
|             prefix += '.' | ||||
|         self.build_method_map(dict, prefix) | ||||
| 
 | ||||
|     def add_method(self, f=None, name=None): | ||||
|         """ Add a method to the dispatcher. | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         f : callable | ||||
|             Callable to be added. | ||||
|         name : str, optional | ||||
|             Name to register (the default is function **f** name) | ||||
| 
 | ||||
|         Notes | ||||
|         ----- | ||||
|         When used as a decorator keeps callable object unmodified. | ||||
| 
 | ||||
|         Examples | ||||
|         -------- | ||||
| 
 | ||||
|         Use as method | ||||
| 
 | ||||
|         >>> d = Dispatcher() | ||||
|         >>> d.add_method(lambda a, b: a + b, name="sum") | ||||
|         <function __main__.<lambda>> | ||||
| 
 | ||||
|         Or use as decorator | ||||
| 
 | ||||
|         >>> d = Dispatcher() | ||||
|         >>> @d.add_method | ||||
|             def mymethod(*args, **kwargs): | ||||
|                 print(args, kwargs) | ||||
| 
 | ||||
|         Or use as a decorator with a different function name | ||||
|         >>> d = Dispatcher() | ||||
|         >>> @d.add_method(name="my.method") | ||||
|             def mymethod(*args, **kwargs): | ||||
|                 print(args, kwargs) | ||||
| 
 | ||||
|         """ | ||||
|         if name and not f: | ||||
|             return functools.partial(self.add_method, name=name) | ||||
| 
 | ||||
|         self.method_map[name or f.__name__] = f | ||||
|         return f | ||||
| 
 | ||||
|     def build_method_map(self, prototype, prefix=''): | ||||
|         """ Add prototype methods to the dispatcher. | ||||
| 
 | ||||
|         Parameters | ||||
|         ---------- | ||||
|         prototype : object or dict | ||||
|             Initial method mapping. | ||||
|             If given prototype is a dictionary then all callable objects will | ||||
|             be added to dispatcher. | ||||
|             If given prototype is an object then all public methods will | ||||
|             be used. | ||||
|         prefix: string, optional | ||||
|             Prefix of methods | ||||
| 
 | ||||
|         """ | ||||
|         if not isinstance(prototype, dict): | ||||
|             prototype = dict((method, getattr(prototype, method)) | ||||
|                              for method in dir(prototype) | ||||
|                              if not method.startswith('_')) | ||||
| 
 | ||||
|         for attr, method in prototype.items(): | ||||
|             if callable(method): | ||||
|                 self[prefix + attr] = method | ||||
| @ -0,0 +1,185 @@ | ||||
| """ JSON-RPC Exceptions.""" | ||||
| from . import six | ||||
| import json | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCError(object): | ||||
| 
 | ||||
|     """ Error for JSON-RPC communication. | ||||
| 
 | ||||
|     When a rpc call encounters an error, the Response Object MUST contain the | ||||
|     error member with a value that is a Object with the following members: | ||||
| 
 | ||||
|     Parameters | ||||
|     ---------- | ||||
|     code: int | ||||
|         A Number that indicates the error type that occurred. | ||||
|         This MUST be an integer. | ||||
|         The error codes from and including -32768 to -32000 are reserved for | ||||
|         pre-defined errors. Any code within this range, but not defined | ||||
|         explicitly below is reserved for future use. The error codes are nearly | ||||
|         the same as those suggested for XML-RPC at the following | ||||
|         url: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php | ||||
| 
 | ||||
|     message: str | ||||
|         A String providing a short description of the error. | ||||
|         The message SHOULD be limited to a concise single sentence. | ||||
| 
 | ||||
|     data: int or str or dict or list, optional | ||||
|         A Primitive or Structured value that contains additional | ||||
|         information about the error. | ||||
|         This may be omitted. | ||||
|         The value of this member is defined by the Server (e.g. detailed error | ||||
|         information, nested errors etc.). | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     serialize = staticmethod(json.dumps) | ||||
|     deserialize = staticmethod(json.loads) | ||||
| 
 | ||||
|     def __init__(self, code=None, message=None, data=None): | ||||
|         self._data = dict() | ||||
|         self.code = getattr(self.__class__, "CODE", code) | ||||
|         self.message = getattr(self.__class__, "MESSAGE", message) | ||||
|         self.data = data | ||||
| 
 | ||||
|     def __get_code(self): | ||||
|         return self._data["code"] | ||||
| 
 | ||||
|     def __set_code(self, value): | ||||
|         if not isinstance(value, six.integer_types): | ||||
|             raise ValueError("Error code should be integer") | ||||
| 
 | ||||
|         self._data["code"] = value | ||||
| 
 | ||||
|     code = property(__get_code, __set_code) | ||||
| 
 | ||||
|     def __get_message(self): | ||||
|         return self._data["message"] | ||||
| 
 | ||||
|     def __set_message(self, value): | ||||
|         if not isinstance(value, six.string_types): | ||||
|             raise ValueError("Error message should be string") | ||||
| 
 | ||||
|         self._data["message"] = value | ||||
| 
 | ||||
|     message = property(__get_message, __set_message) | ||||
| 
 | ||||
|     def __get_data(self): | ||||
|         return self._data.get("data") | ||||
| 
 | ||||
|     def __set_data(self, value): | ||||
|         if value is not None: | ||||
|             self._data["data"] = value | ||||
| 
 | ||||
|     data = property(__get_data, __set_data) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def from_json(cls, json_str): | ||||
|         data = cls.deserialize(json_str) | ||||
|         return cls( | ||||
|             code=data["code"], message=data["message"], data=data.get("data")) | ||||
| 
 | ||||
|     @property | ||||
|     def json(self): | ||||
|         return self.serialize(self._data) | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCParseError(JSONRPCError): | ||||
| 
 | ||||
|     """ Parse Error. | ||||
| 
 | ||||
|     Invalid JSON was received by the server. | ||||
|     An error occurred on the server while parsing the JSON text. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     CODE = -32700 | ||||
|     MESSAGE = "Parse error" | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCInvalidRequest(JSONRPCError): | ||||
| 
 | ||||
|     """ Invalid Request. | ||||
| 
 | ||||
|     The JSON sent is not a valid Request object. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     CODE = -32600 | ||||
|     MESSAGE = "Invalid Request" | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCMethodNotFound(JSONRPCError): | ||||
| 
 | ||||
|     """ Method not found. | ||||
| 
 | ||||
|     The method does not exist / is not available. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     CODE = -32601 | ||||
|     MESSAGE = "Method not found" | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCInvalidParams(JSONRPCError): | ||||
| 
 | ||||
|     """ Invalid params. | ||||
| 
 | ||||
|     Invalid method parameter(s). | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     CODE = -32602 | ||||
|     MESSAGE = "Invalid params" | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCInternalError(JSONRPCError): | ||||
| 
 | ||||
|     """ Internal error. | ||||
| 
 | ||||
|     Internal JSON-RPC error. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     CODE = -32603 | ||||
|     MESSAGE = "Internal error" | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCServerError(JSONRPCError): | ||||
| 
 | ||||
|     """ Server error. | ||||
| 
 | ||||
|     Reserved for implementation-defined server-errors. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     CODE = -32000 | ||||
|     MESSAGE = "Server error" | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCException(Exception): | ||||
| 
 | ||||
|     """ JSON-RPC Exception.""" | ||||
| 
 | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCInvalidRequestException(JSONRPCException): | ||||
| 
 | ||||
|     """ Request is not valid.""" | ||||
| 
 | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCDispatchException(JSONRPCException): | ||||
| 
 | ||||
|     """ JSON-RPC Dispatch Exception. | ||||
| 
 | ||||
|     Should be thrown in dispatch methods. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, code=None, message=None, data=None, *args, **kwargs): | ||||
|         super(JSONRPCDispatchException, self).__init__(args, kwargs) | ||||
|         self.error = JSONRPCError(code=code, data=data, message=message) | ||||
| @ -0,0 +1,151 @@ | ||||
| from . import six | ||||
| 
 | ||||
| from .base import JSONRPCBaseRequest, JSONRPCBaseResponse | ||||
| from .exceptions import JSONRPCInvalidRequestException, JSONRPCError | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPC10Request(JSONRPCBaseRequest): | ||||
| 
 | ||||
|     """ JSON-RPC 1.0 Request. | ||||
| 
 | ||||
|     A remote method is invoked by sending a request to a remote service. | ||||
|     The request is a single object serialized using json. | ||||
| 
 | ||||
|     :param str method: The name of the method to be invoked. | ||||
|     :param list params: An Array of objects to pass as arguments to the method. | ||||
|     :param _id: This can be of any type. It is used to match the response with | ||||
|         the request that it is replying to. | ||||
|     :param bool is_notification: whether request notification or not. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     JSONRPC_VERSION = "1.0" | ||||
|     REQUIRED_FIELDS = set(["method", "params", "id"]) | ||||
|     POSSIBLE_FIELDS = set(["method", "params", "id"]) | ||||
| 
 | ||||
|     @property | ||||
|     def data(self): | ||||
|         data = dict((k, v) for k, v in self._data.items()) | ||||
|         data["id"] = None if self.is_notification else data["id"] | ||||
|         return data | ||||
| 
 | ||||
|     @data.setter | ||||
|     def data(self, value): | ||||
|         if not isinstance(value, dict): | ||||
|             raise ValueError("data should be dict") | ||||
| 
 | ||||
|         self._data = value | ||||
| 
 | ||||
|     @property | ||||
|     def method(self): | ||||
|         return self._data.get("method") | ||||
| 
 | ||||
|     @method.setter | ||||
|     def method(self, value): | ||||
|         if not isinstance(value, six.string_types): | ||||
|             raise ValueError("Method should be string") | ||||
| 
 | ||||
|         self._data["method"] = str(value) | ||||
| 
 | ||||
|     @property | ||||
|     def params(self): | ||||
|         return self._data.get("params") | ||||
| 
 | ||||
|     @params.setter | ||||
|     def params(self, value): | ||||
|         if not isinstance(value, (list, tuple)): | ||||
|             raise ValueError("Incorrect params {0}".format(value)) | ||||
| 
 | ||||
|         self._data["params"] = list(value) | ||||
| 
 | ||||
|     @property | ||||
|     def _id(self): | ||||
|         return self._data.get("id") | ||||
| 
 | ||||
|     @_id.setter | ||||
|     def _id(self, value): | ||||
|         self._data["id"] = value | ||||
| 
 | ||||
|     @property | ||||
|     def is_notification(self): | ||||
|         return self._data["id"] is None or self._is_notification | ||||
| 
 | ||||
|     @is_notification.setter | ||||
|     def is_notification(self, value): | ||||
|         if value is None: | ||||
|             value = self._id is None | ||||
| 
 | ||||
|         if self._id is None and not value: | ||||
|             raise ValueError("Can not set attribute is_notification. " + | ||||
|                              "Request id should not be None") | ||||
| 
 | ||||
|         self._is_notification = value | ||||
| 
 | ||||
|     @classmethod | ||||
|     def from_json(cls, json_str): | ||||
|         data = cls.deserialize(json_str) | ||||
|         return cls.from_data(data) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def from_data(cls, data): | ||||
|         if not isinstance(data, dict): | ||||
|             raise ValueError("data should be dict") | ||||
| 
 | ||||
|         if cls.REQUIRED_FIELDS <= set(data.keys()) <= cls.POSSIBLE_FIELDS: | ||||
|             return cls( | ||||
|                 method=data["method"], params=data["params"], _id=data["id"] | ||||
|             ) | ||||
|         else: | ||||
|             extra = set(data.keys()) - cls.POSSIBLE_FIELDS | ||||
|             missed = cls.REQUIRED_FIELDS - set(data.keys()) | ||||
|             msg = "Invalid request. Extra fields: {0}, Missed fields: {1}" | ||||
|             raise JSONRPCInvalidRequestException(msg.format(extra, missed)) | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPC10Response(JSONRPCBaseResponse): | ||||
| 
 | ||||
|     JSONRPC_VERSION = "1.0" | ||||
| 
 | ||||
|     @property | ||||
|     def data(self): | ||||
|         data = dict((k, v) for k, v in self._data.items()) | ||||
|         return data | ||||
| 
 | ||||
|     @data.setter | ||||
|     def data(self, value): | ||||
|         if not isinstance(value, dict): | ||||
|             raise ValueError("data should be dict") | ||||
| 
 | ||||
|         self._data = value | ||||
| 
 | ||||
|     @property | ||||
|     def result(self): | ||||
|         return self._data.get("result") | ||||
| 
 | ||||
|     @result.setter | ||||
|     def result(self, value): | ||||
|         if self.error: | ||||
|             raise ValueError("Either result or error should be used") | ||||
|         self._data["result"] = value | ||||
| 
 | ||||
|     @property | ||||
|     def error(self): | ||||
|         return self._data.get("error") | ||||
| 
 | ||||
|     @error.setter | ||||
|     def error(self, value): | ||||
|         self._data.pop('value', None) | ||||
|         if value: | ||||
|             self._data["error"] = value | ||||
|             # Test error | ||||
|             JSONRPCError(**value) | ||||
| 
 | ||||
|     @property | ||||
|     def _id(self): | ||||
|         return self._data.get("id") | ||||
| 
 | ||||
|     @_id.setter | ||||
|     def _id(self, value): | ||||
|         if value is None: | ||||
|             raise ValueError("id could not be null for JSON-RPC1.0 Response") | ||||
|         self._data["id"] = value | ||||
| @ -0,0 +1,136 @@ | ||||
| import json | ||||
| import logging | ||||
| from .utils import is_invalid_params | ||||
| from .exceptions import ( | ||||
|     JSONRPCInvalidParams, | ||||
|     JSONRPCInvalidRequest, | ||||
|     JSONRPCInvalidRequestException, | ||||
|     JSONRPCMethodNotFound, | ||||
|     JSONRPCParseError, | ||||
|     JSONRPCServerError, | ||||
|     JSONRPCDispatchException, | ||||
| ) | ||||
| from .jsonrpc1 import JSONRPC10Response | ||||
| from .jsonrpc2 import ( | ||||
|     JSONRPC20BatchRequest, | ||||
|     JSONRPC20BatchResponse, | ||||
|     JSONRPC20Response, | ||||
| ) | ||||
| from .jsonrpc import JSONRPCRequest | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class JSONRPCResponseManager(object): | ||||
| 
 | ||||
|     """ JSON-RPC response manager. | ||||
| 
 | ||||
|     Method brings syntactic sugar into library. Given dispatcher it handles | ||||
|     request (both single and batch) and handles errors. | ||||
|     Request could be handled in parallel, it is server responsibility. | ||||
| 
 | ||||
|     :param str request_str: json string. Will be converted into | ||||
|         JSONRPC20Request, JSONRPC20BatchRequest or JSONRPC10Request | ||||
| 
 | ||||
|     :param dict dispather: dict<function_name:function>. | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     RESPONSE_CLASS_MAP = { | ||||
|         "1.0": JSONRPC10Response, | ||||
|         "2.0": JSONRPC20Response, | ||||
|     } | ||||
| 
 | ||||
|     @classmethod | ||||
|     def handle(cls, request_str, dispatcher): | ||||
|         if isinstance(request_str, bytes): | ||||
|             request_str = request_str.decode("utf-8") | ||||
| 
 | ||||
|         try: | ||||
|             data = json.loads(request_str) | ||||
|         except (TypeError, ValueError): | ||||
|             return JSONRPC20Response(error=JSONRPCParseError()._data) | ||||
| 
 | ||||
|         try: | ||||
|             request = JSONRPCRequest.from_data(data) | ||||
|         except JSONRPCInvalidRequestException: | ||||
|             return JSONRPC20Response(error=JSONRPCInvalidRequest()._data) | ||||
| 
 | ||||
|         return cls.handle_request(request, dispatcher) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def handle_request(cls, request, dispatcher): | ||||
|         """ Handle request data. | ||||
| 
 | ||||
|         At this moment request has correct jsonrpc format. | ||||
| 
 | ||||
|         :param dict request: data parsed from request_str. | ||||
|         :param jsonrpc.dispatcher.Dispatcher dispatcher: | ||||
| 
 | ||||
|         .. versionadded: 1.8.0 | ||||
| 
 | ||||
|         """ | ||||
|         rs = request if isinstance(request, JSONRPC20BatchRequest) \ | ||||
|             else [request] | ||||
|         responses = [r for r in cls._get_responses(rs, dispatcher) | ||||
|                      if r is not None] | ||||
| 
 | ||||
|         # notifications | ||||
|         if not responses: | ||||
|             return | ||||
| 
 | ||||
|         if isinstance(request, JSONRPC20BatchRequest): | ||||
|             response = JSONRPC20BatchResponse(*responses) | ||||
|             response.request = request | ||||
|             return response | ||||
|         else: | ||||
|             return responses[0] | ||||
| 
 | ||||
|     @classmethod | ||||
|     def _get_responses(cls, requests, dispatcher): | ||||
|         """ Response to each single JSON-RPC Request. | ||||
| 
 | ||||
|         :return iterator(JSONRPC20Response): | ||||
| 
 | ||||
|         .. versionadded: 1.9.0 | ||||
|           TypeError inside the function is distinguished from Invalid Params. | ||||
| 
 | ||||
|         """ | ||||
|         for request in requests: | ||||
|             def make_response(**kwargs): | ||||
|                 response = cls.RESPONSE_CLASS_MAP[request.JSONRPC_VERSION]( | ||||
|                     _id=request._id, **kwargs) | ||||
|                 response.request = request | ||||
|                 return response | ||||
| 
 | ||||
|             output = None | ||||
|             try: | ||||
|                 method = dispatcher[request.method] | ||||
|             except KeyError: | ||||
|                 output = make_response(error=JSONRPCMethodNotFound()._data) | ||||
|             else: | ||||
|                 try: | ||||
|                     result = method(*request.args, **request.kwargs) | ||||
|                 except JSONRPCDispatchException as e: | ||||
|                     output = make_response(error=e.error._data) | ||||
|                 except Exception as e: | ||||
|                     data = { | ||||
|                         "type": e.__class__.__name__, | ||||
|                         "args": e.args, | ||||
|                         "message": str(e), | ||||
|                     } | ||||
| 
 | ||||
|                     logger.exception("API Exception: {0}".format(data)) | ||||
| 
 | ||||
|                     if isinstance(e, TypeError) and is_invalid_params( | ||||
|                             method, *request.args, **request.kwargs): | ||||
|                         output = make_response( | ||||
|                             error=JSONRPCInvalidParams(data=data)._data) | ||||
|                     else: | ||||
|                         output = make_response( | ||||
|                             error=JSONRPCServerError(data=data)._data) | ||||
|                 else: | ||||
|                     output = make_response(result=result) | ||||
|             finally: | ||||
|                 if not request.is_notification: | ||||
|                     yield output | ||||
| @ -0,0 +1,584 @@ | ||||
| """Utilities for writing code that runs on Python 2 and 3""" | ||||
| 
 | ||||
| # Copyright (c) 2010-2013 Benjamin Peterson | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to deal | ||||
| # in the Software without restriction, including without limitation the rights | ||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| # copies of the Software, and to permit persons to whom the Software is | ||||
| # furnished to do so, subject to the following conditions: | ||||
| # | ||||
| # The above copyright notice and this permission notice shall be included in all | ||||
| # copies or substantial portions of the Software. | ||||
| # | ||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| # SOFTWARE. | ||||
| 
 | ||||
| import operator | ||||
| import sys | ||||
| import types | ||||
| 
 | ||||
| __author__ = "Benjamin Peterson <benjamin@python.org>" | ||||
| __version__ = "1.4.1" | ||||
| 
 | ||||
| 
 | ||||
| # Useful for very coarse version differentiation. | ||||
| PY2 = sys.version_info[0] == 2 | ||||
| PY3 = sys.version_info[0] == 3 | ||||
| 
 | ||||
| if PY3: | ||||
|     string_types = str, | ||||
|     integer_types = int, | ||||
|     class_types = type, | ||||
|     text_type = str | ||||
|     binary_type = bytes | ||||
| 
 | ||||
|     MAXSIZE = sys.maxsize | ||||
| else: | ||||
|     string_types = basestring, | ||||
|     integer_types = (int, long) | ||||
|     class_types = (type, types.ClassType) | ||||
|     text_type = unicode | ||||
|     binary_type = str | ||||
| 
 | ||||
|     if sys.platform.startswith("java"): | ||||
|         # Jython always uses 32 bits. | ||||
|         MAXSIZE = int((1 << 31) - 1) | ||||
|     else: | ||||
|         # It's possible to have sizeof(long) != sizeof(Py_ssize_t). | ||||
|         class X(object): | ||||
|             def __len__(self): | ||||
|                 return 1 << 31 | ||||
|         try: | ||||
|             len(X()) | ||||
|         except OverflowError: | ||||
|             # 32-bit | ||||
|             MAXSIZE = int((1 << 31) - 1) | ||||
|         else: | ||||
|             # 64-bit | ||||
|             MAXSIZE = int((1 << 63) - 1) | ||||
|         del X | ||||
| 
 | ||||
| 
 | ||||
| def _add_doc(func, doc): | ||||
|     """Add documentation to a function.""" | ||||
|     func.__doc__ = doc | ||||
| 
 | ||||
| 
 | ||||
| def _import_module(name): | ||||
|     """Import module, returning the module after the last dot.""" | ||||
|     __import__(name) | ||||
|     return sys.modules[name] | ||||
| 
 | ||||
| 
 | ||||
| class _LazyDescr(object): | ||||
| 
 | ||||
|     def __init__(self, name): | ||||
|         self.name = name | ||||
| 
 | ||||
|     def __get__(self, obj, tp): | ||||
|         result = self._resolve() | ||||
|         setattr(obj, self.name, result) | ||||
|         # This is a bit ugly, but it avoids running this again. | ||||
|         delattr(tp, self.name) | ||||
|         return result | ||||
| 
 | ||||
| 
 | ||||
| class MovedModule(_LazyDescr): | ||||
| 
 | ||||
|     def __init__(self, name, old, new=None): | ||||
|         super(MovedModule, self).__init__(name) | ||||
|         if PY3: | ||||
|             if new is None: | ||||
|                 new = name | ||||
|             self.mod = new | ||||
|         else: | ||||
|             self.mod = old | ||||
| 
 | ||||
|     def _resolve(self): | ||||
|         return _import_module(self.mod) | ||||
| 
 | ||||
| 
 | ||||
| class MovedAttribute(_LazyDescr): | ||||
| 
 | ||||
|     def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): | ||||
|         super(MovedAttribute, self).__init__(name) | ||||
|         if PY3: | ||||
|             if new_mod is None: | ||||
|                 new_mod = name | ||||
|             self.mod = new_mod | ||||
|             if new_attr is None: | ||||
|                 if old_attr is None: | ||||
|                     new_attr = name | ||||
|                 else: | ||||
|                     new_attr = old_attr | ||||
|             self.attr = new_attr | ||||
|         else: | ||||
|             self.mod = old_mod | ||||
|             if old_attr is None: | ||||
|                 old_attr = name | ||||
|             self.attr = old_attr | ||||
| 
 | ||||
|     def _resolve(self): | ||||
|         module = _import_module(self.mod) | ||||
|         return getattr(module, self.attr) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class _MovedItems(types.ModuleType): | ||||
|     """Lazy loading of moved objects""" | ||||
| 
 | ||||
| 
 | ||||
| _moved_attributes = [ | ||||
|     MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), | ||||
|     MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), | ||||
|     MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), | ||||
|     MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), | ||||
|     MovedAttribute("map", "itertools", "builtins", "imap", "map"), | ||||
|     MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), | ||||
|     MovedAttribute("reload_module", "__builtin__", "imp", "reload"), | ||||
|     MovedAttribute("reduce", "__builtin__", "functools"), | ||||
|     MovedAttribute("StringIO", "StringIO", "io"), | ||||
|     MovedAttribute("UserString", "UserString", "collections"), | ||||
|     MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), | ||||
|     MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), | ||||
|     MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), | ||||
| 
 | ||||
|     MovedModule("builtins", "__builtin__"), | ||||
|     MovedModule("configparser", "ConfigParser"), | ||||
|     MovedModule("copyreg", "copy_reg"), | ||||
|     MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), | ||||
|     MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), | ||||
|     MovedModule("http_cookies", "Cookie", "http.cookies"), | ||||
|     MovedModule("html_entities", "htmlentitydefs", "html.entities"), | ||||
|     MovedModule("html_parser", "HTMLParser", "html.parser"), | ||||
|     MovedModule("http_client", "httplib", "http.client"), | ||||
|     MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), | ||||
|     MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), | ||||
|     MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), | ||||
|     MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), | ||||
|     MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), | ||||
|     MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), | ||||
|     MovedModule("cPickle", "cPickle", "pickle"), | ||||
|     MovedModule("queue", "Queue"), | ||||
|     MovedModule("reprlib", "repr"), | ||||
|     MovedModule("socketserver", "SocketServer"), | ||||
|     MovedModule("_thread", "thread", "_thread"), | ||||
|     MovedModule("tkinter", "Tkinter"), | ||||
|     MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), | ||||
|     MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), | ||||
|     MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), | ||||
|     MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), | ||||
|     MovedModule("tkinter_tix", "Tix", "tkinter.tix"), | ||||
|     MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), | ||||
|     MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), | ||||
|     MovedModule("tkinter_colorchooser", "tkColorChooser", | ||||
|                 "tkinter.colorchooser"), | ||||
|     MovedModule("tkinter_commondialog", "tkCommonDialog", | ||||
|                 "tkinter.commondialog"), | ||||
|     MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), | ||||
|     MovedModule("tkinter_font", "tkFont", "tkinter.font"), | ||||
|     MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), | ||||
|     MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", | ||||
|                 "tkinter.simpledialog"), | ||||
|     MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), | ||||
|     MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), | ||||
|     MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), | ||||
|     MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), | ||||
|     MovedModule("winreg", "_winreg"), | ||||
| ] | ||||
| for attr in _moved_attributes: | ||||
|     setattr(_MovedItems, attr.name, attr) | ||||
| del attr | ||||
| 
 | ||||
| moves = sys.modules[__name__ + ".moves"] = _MovedItems(__name__ + ".moves") | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class Module_six_moves_urllib_parse(types.ModuleType): | ||||
|     """Lazy loading of moved objects in six.moves.urllib_parse""" | ||||
| 
 | ||||
| 
 | ||||
| _urllib_parse_moved_attributes = [ | ||||
|     MovedAttribute("ParseResult", "urlparse", "urllib.parse"), | ||||
|     MovedAttribute("parse_qs", "urlparse", "urllib.parse"), | ||||
|     MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), | ||||
|     MovedAttribute("urldefrag", "urlparse", "urllib.parse"), | ||||
|     MovedAttribute("urljoin", "urlparse", "urllib.parse"), | ||||
|     MovedAttribute("urlparse", "urlparse", "urllib.parse"), | ||||
|     MovedAttribute("urlsplit", "urlparse", "urllib.parse"), | ||||
|     MovedAttribute("urlunparse", "urlparse", "urllib.parse"), | ||||
|     MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), | ||||
|     MovedAttribute("quote", "urllib", "urllib.parse"), | ||||
|     MovedAttribute("quote_plus", "urllib", "urllib.parse"), | ||||
|     MovedAttribute("unquote", "urllib", "urllib.parse"), | ||||
|     MovedAttribute("unquote_plus", "urllib", "urllib.parse"), | ||||
|     MovedAttribute("urlencode", "urllib", "urllib.parse"), | ||||
| ] | ||||
| for attr in _urllib_parse_moved_attributes: | ||||
|     setattr(Module_six_moves_urllib_parse, attr.name, attr) | ||||
| del attr | ||||
| 
 | ||||
| sys.modules[__name__ + ".moves.urllib_parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse") | ||||
| sys.modules[__name__ + ".moves.urllib.parse"] = Module_six_moves_urllib_parse(__name__ + ".moves.urllib.parse") | ||||
| 
 | ||||
| 
 | ||||
| class Module_six_moves_urllib_error(types.ModuleType): | ||||
|     """Lazy loading of moved objects in six.moves.urllib_error""" | ||||
| 
 | ||||
| 
 | ||||
| _urllib_error_moved_attributes = [ | ||||
|     MovedAttribute("URLError", "urllib2", "urllib.error"), | ||||
|     MovedAttribute("HTTPError", "urllib2", "urllib.error"), | ||||
|     MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), | ||||
| ] | ||||
| for attr in _urllib_error_moved_attributes: | ||||
|     setattr(Module_six_moves_urllib_error, attr.name, attr) | ||||
| del attr | ||||
| 
 | ||||
| sys.modules[__name__ + ".moves.urllib_error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib_error") | ||||
| sys.modules[__name__ + ".moves.urllib.error"] = Module_six_moves_urllib_error(__name__ + ".moves.urllib.error") | ||||
| 
 | ||||
| 
 | ||||
| class Module_six_moves_urllib_request(types.ModuleType): | ||||
|     """Lazy loading of moved objects in six.moves.urllib_request""" | ||||
| 
 | ||||
| 
 | ||||
| _urllib_request_moved_attributes = [ | ||||
|     MovedAttribute("urlopen", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("install_opener", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("build_opener", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("pathname2url", "urllib", "urllib.request"), | ||||
|     MovedAttribute("url2pathname", "urllib", "urllib.request"), | ||||
|     MovedAttribute("getproxies", "urllib", "urllib.request"), | ||||
|     MovedAttribute("Request", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("BaseHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("FileHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("FTPHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), | ||||
|     MovedAttribute("urlretrieve", "urllib", "urllib.request"), | ||||
|     MovedAttribute("urlcleanup", "urllib", "urllib.request"), | ||||
|     MovedAttribute("URLopener", "urllib", "urllib.request"), | ||||
|     MovedAttribute("FancyURLopener", "urllib", "urllib.request"), | ||||
| ] | ||||
| for attr in _urllib_request_moved_attributes: | ||||
|     setattr(Module_six_moves_urllib_request, attr.name, attr) | ||||
| del attr | ||||
| 
 | ||||
| sys.modules[__name__ + ".moves.urllib_request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib_request") | ||||
| sys.modules[__name__ + ".moves.urllib.request"] = Module_six_moves_urllib_request(__name__ + ".moves.urllib.request") | ||||
| 
 | ||||
| 
 | ||||
| class Module_six_moves_urllib_response(types.ModuleType): | ||||
|     """Lazy loading of moved objects in six.moves.urllib_response""" | ||||
| 
 | ||||
| 
 | ||||
| _urllib_response_moved_attributes = [ | ||||
|     MovedAttribute("addbase", "urllib", "urllib.response"), | ||||
|     MovedAttribute("addclosehook", "urllib", "urllib.response"), | ||||
|     MovedAttribute("addinfo", "urllib", "urllib.response"), | ||||
|     MovedAttribute("addinfourl", "urllib", "urllib.response"), | ||||
| ] | ||||
| for attr in _urllib_response_moved_attributes: | ||||
|     setattr(Module_six_moves_urllib_response, attr.name, attr) | ||||
| del attr | ||||
| 
 | ||||
| sys.modules[__name__ + ".moves.urllib_response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib_response") | ||||
| sys.modules[__name__ + ".moves.urllib.response"] = Module_six_moves_urllib_response(__name__ + ".moves.urllib.response") | ||||
| 
 | ||||
| 
 | ||||
| class Module_six_moves_urllib_robotparser(types.ModuleType): | ||||
|     """Lazy loading of moved objects in six.moves.urllib_robotparser""" | ||||
| 
 | ||||
| 
 | ||||
| _urllib_robotparser_moved_attributes = [ | ||||
|     MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), | ||||
| ] | ||||
| for attr in _urllib_robotparser_moved_attributes: | ||||
|     setattr(Module_six_moves_urllib_robotparser, attr.name, attr) | ||||
| del attr | ||||
| 
 | ||||
| sys.modules[__name__ + ".moves.urllib_robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib_robotparser") | ||||
| sys.modules[__name__ + ".moves.urllib.robotparser"] = Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser") | ||||
| 
 | ||||
| 
 | ||||
| class Module_six_moves_urllib(types.ModuleType): | ||||
|     """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" | ||||
|     parse = sys.modules[__name__ + ".moves.urllib_parse"] | ||||
|     error = sys.modules[__name__ + ".moves.urllib_error"] | ||||
|     request = sys.modules[__name__ + ".moves.urllib_request"] | ||||
|     response = sys.modules[__name__ + ".moves.urllib_response"] | ||||
|     robotparser = sys.modules[__name__ + ".moves.urllib_robotparser"] | ||||
| 
 | ||||
| 
 | ||||
| sys.modules[__name__ + ".moves.urllib"] = Module_six_moves_urllib(__name__ + ".moves.urllib") | ||||
| 
 | ||||
| 
 | ||||
| def add_move(move): | ||||
|     """Add an item to six.moves.""" | ||||
|     setattr(_MovedItems, move.name, move) | ||||
| 
 | ||||
| 
 | ||||
| def remove_move(name): | ||||
|     """Remove item from six.moves.""" | ||||
|     try: | ||||
|         delattr(_MovedItems, name) | ||||
|     except AttributeError: | ||||
|         try: | ||||
|             del moves.__dict__[name] | ||||
|         except KeyError: | ||||
|             raise AttributeError("no such move, %r" % (name,)) | ||||
| 
 | ||||
| 
 | ||||
| if PY3: | ||||
|     _meth_func = "__func__" | ||||
|     _meth_self = "__self__" | ||||
| 
 | ||||
|     _func_closure = "__closure__" | ||||
|     _func_code = "__code__" | ||||
|     _func_defaults = "__defaults__" | ||||
|     _func_globals = "__globals__" | ||||
| 
 | ||||
|     _iterkeys = "keys" | ||||
|     _itervalues = "values" | ||||
|     _iteritems = "items" | ||||
|     _iterlists = "lists" | ||||
| else: | ||||
|     _meth_func = "im_func" | ||||
|     _meth_self = "im_self" | ||||
| 
 | ||||
|     _func_closure = "func_closure" | ||||
|     _func_code = "func_code" | ||||
|     _func_defaults = "func_defaults" | ||||
|     _func_globals = "func_globals" | ||||
| 
 | ||||
|     _iterkeys = "iterkeys" | ||||
|     _itervalues = "itervalues" | ||||
|     _iteritems = "iteritems" | ||||
|     _iterlists = "iterlists" | ||||
| 
 | ||||
| 
 | ||||
| try: | ||||
|     advance_iterator = next | ||||
| except NameError: | ||||
|     def advance_iterator(it): | ||||
|         return it.next() | ||||
| next = advance_iterator | ||||
| 
 | ||||
| 
 | ||||
| try: | ||||
|     callable = callable | ||||
| except NameError: | ||||
|     def callable(obj): | ||||
|         return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) | ||||
| 
 | ||||
| 
 | ||||
| if PY3: | ||||
|     def get_unbound_function(unbound): | ||||
|         return unbound | ||||
| 
 | ||||
|     create_bound_method = types.MethodType | ||||
| 
 | ||||
|     Iterator = object | ||||
| else: | ||||
|     def get_unbound_function(unbound): | ||||
|         return unbound.im_func | ||||
| 
 | ||||
|     def create_bound_method(func, obj): | ||||
|         return types.MethodType(func, obj, obj.__class__) | ||||
| 
 | ||||
|     class Iterator(object): | ||||
| 
 | ||||
|         def next(self): | ||||
|             return type(self).__next__(self) | ||||
| 
 | ||||
|     callable = callable | ||||
| _add_doc(get_unbound_function, | ||||
|          """Get the function out of a possibly unbound function""") | ||||
| 
 | ||||
| 
 | ||||
| get_method_function = operator.attrgetter(_meth_func) | ||||
| get_method_self = operator.attrgetter(_meth_self) | ||||
| get_function_closure = operator.attrgetter(_func_closure) | ||||
| get_function_code = operator.attrgetter(_func_code) | ||||
| get_function_defaults = operator.attrgetter(_func_defaults) | ||||
| get_function_globals = operator.attrgetter(_func_globals) | ||||
| 
 | ||||
| 
 | ||||
| def iterkeys(d, **kw): | ||||
|     """Return an iterator over the keys of a dictionary.""" | ||||
|     return iter(getattr(d, _iterkeys)(**kw)) | ||||
| 
 | ||||
| def itervalues(d, **kw): | ||||
|     """Return an iterator over the values of a dictionary.""" | ||||
|     return iter(getattr(d, _itervalues)(**kw)) | ||||
| 
 | ||||
| def iteritems(d, **kw): | ||||
|     """Return an iterator over the (key, value) pairs of a dictionary.""" | ||||
|     return iter(getattr(d, _iteritems)(**kw)) | ||||
| 
 | ||||
| def iterlists(d, **kw): | ||||
|     """Return an iterator over the (key, [values]) pairs of a dictionary.""" | ||||
|     return iter(getattr(d, _iterlists)(**kw)) | ||||
| 
 | ||||
| 
 | ||||
| if PY3: | ||||
|     def b(s): | ||||
|         return s.encode("latin-1") | ||||
|     def u(s): | ||||
|         return s | ||||
|     unichr = chr | ||||
|     if sys.version_info[1] <= 1: | ||||
|         def int2byte(i): | ||||
|             return bytes((i,)) | ||||
|     else: | ||||
|         # This is about 2x faster than the implementation above on 3.2+ | ||||
|         int2byte = operator.methodcaller("to_bytes", 1, "big") | ||||
|     byte2int = operator.itemgetter(0) | ||||
|     indexbytes = operator.getitem | ||||
|     iterbytes = iter | ||||
|     import io | ||||
|     StringIO = io.StringIO | ||||
|     BytesIO = io.BytesIO | ||||
| else: | ||||
|     def b(s): | ||||
|         return s | ||||
|     def u(s): | ||||
|         return unicode(s, "unicode_escape") | ||||
|     unichr = unichr | ||||
|     int2byte = chr | ||||
|     def byte2int(bs): | ||||
|         return ord(bs[0]) | ||||
|     def indexbytes(buf, i): | ||||
|         return ord(buf[i]) | ||||
|     def iterbytes(buf): | ||||
|         return (ord(byte) for byte in buf) | ||||
|     import StringIO | ||||
|     StringIO = BytesIO = StringIO.StringIO | ||||
| _add_doc(b, """Byte literal""") | ||||
| _add_doc(u, """Text literal""") | ||||
| 
 | ||||
| 
 | ||||
| if PY3: | ||||
|     exec_ = getattr(moves.builtins, "exec") | ||||
| 
 | ||||
| 
 | ||||
|     def reraise(tp, value, tb=None): | ||||
|         if value.__traceback__ is not tb: | ||||
|             raise value.with_traceback(tb) | ||||
|         raise value | ||||
| 
 | ||||
| else: | ||||
|     def exec_(_code_, _globs_=None, _locs_=None): | ||||
|         """Execute code in a namespace.""" | ||||
|         if _globs_ is None: | ||||
|             frame = sys._getframe(1) | ||||
|             _globs_ = frame.f_globals | ||||
|             if _locs_ is None: | ||||
|                 _locs_ = frame.f_locals | ||||
|             del frame | ||||
|         elif _locs_ is None: | ||||
|             _locs_ = _globs_ | ||||
|         exec("""exec _code_ in _globs_, _locs_""") | ||||
| 
 | ||||
| 
 | ||||
|     exec_("""def reraise(tp, value, tb=None): | ||||
|     raise tp, value, tb | ||||
| """) | ||||
| 
 | ||||
| 
 | ||||
| print_ = getattr(moves.builtins, "print", None) | ||||
| if print_ is None: | ||||
|     def print_(*args, **kwargs): | ||||
|         """The new-style print function for Python 2.4 and 2.5.""" | ||||
|         fp = kwargs.pop("file", sys.stdout) | ||||
|         if fp is None: | ||||
|             return | ||||
|         def write(data): | ||||
|             if not isinstance(data, basestring): | ||||
|                 data = str(data) | ||||
|             # If the file has an encoding, encode unicode with it. | ||||
|             if (isinstance(fp, file) and | ||||
|                 isinstance(data, unicode) and | ||||
|                 fp.encoding is not None): | ||||
|                 errors = getattr(fp, "errors", None) | ||||
|                 if errors is None: | ||||
|                     errors = "strict" | ||||
|                 data = data.encode(fp.encoding, errors) | ||||
|             fp.write(data) | ||||
|         want_unicode = False | ||||
|         sep = kwargs.pop("sep", None) | ||||
|         if sep is not None: | ||||
|             if isinstance(sep, unicode): | ||||
|                 want_unicode = True | ||||
|             elif not isinstance(sep, str): | ||||
|                 raise TypeError("sep must be None or a string") | ||||
|         end = kwargs.pop("end", None) | ||||
|         if end is not None: | ||||
|             if isinstance(end, unicode): | ||||
|                 want_unicode = True | ||||
|             elif not isinstance(end, str): | ||||
|                 raise TypeError("end must be None or a string") | ||||
|         if kwargs: | ||||
|             raise TypeError("invalid keyword arguments to print()") | ||||
|         if not want_unicode: | ||||
|             for arg in args: | ||||
|                 if isinstance(arg, unicode): | ||||
|                     want_unicode = True | ||||
|                     break | ||||
|         if want_unicode: | ||||
|             newline = unicode("\n") | ||||
|             space = unicode(" ") | ||||
|         else: | ||||
|             newline = "\n" | ||||
|             space = " " | ||||
|         if sep is None: | ||||
|             sep = space | ||||
|         if end is None: | ||||
|             end = newline | ||||
|         for i, arg in enumerate(args): | ||||
|             if i: | ||||
|                 write(sep) | ||||
|             write(arg) | ||||
|         write(end) | ||||
| 
 | ||||
| _add_doc(reraise, """Reraise an exception.""") | ||||
| 
 | ||||
| 
 | ||||
| def with_metaclass(meta, *bases): | ||||
|     """Create a base class with a metaclass.""" | ||||
|     return meta("NewBase", bases, {}) | ||||
| 
 | ||||
| def add_metaclass(metaclass): | ||||
|     """Class decorator for creating a class with a metaclass.""" | ||||
|     def wrapper(cls): | ||||
|         orig_vars = cls.__dict__.copy() | ||||
|         orig_vars.pop('__dict__', None) | ||||
|         orig_vars.pop('__weakref__', None) | ||||
|         for slots_var in orig_vars.get('__slots__', ()): | ||||
|             orig_vars.pop(slots_var) | ||||
|         return metaclass(cls.__name__, cls.__bases__, orig_vars) | ||||
|     return wrapper | ||||
| @ -0,0 +1,7 @@ | ||||
| # Python3.5+ code. | ||||
| # This won't even parse in earlier versions, so it's kept in a separate file | ||||
| # and imported when needed. | ||||
| 
 | ||||
| 
 | ||||
| def distance(a: float, b: float) -> float: | ||||
|     return (a ** 2 + b ** 2) ** 0.5 | ||||
| @ -0,0 +1,11 @@ | ||||
| SECRET_KEY = 'secret' | ||||
| ROOT_URLCONF = 'jsonrpc.tests.test_backend_django.urls' | ||||
| ALLOWED_HOSTS = ['testserver'] | ||||
| DATABASE_ENGINE = 'django.db.backends.sqlite3' | ||||
| DATABASES = { | ||||
|     'default': { | ||||
|         'ENGINE': 'django.db.backends.sqlite3', | ||||
|         'NAME': ':memory:', | ||||
|     } | ||||
| } | ||||
| JSONRPC_MAP_VIEW_ENABLED = True | ||||
| @ -0,0 +1,89 @@ | ||||
| """ Test Django Backend.""" | ||||
| from __future__ import absolute_import | ||||
| import os | ||||
| 
 | ||||
| try: | ||||
|     from django.core.urlresolvers import RegexURLPattern | ||||
|     from django.test import TestCase | ||||
| except ImportError: | ||||
|     import unittest | ||||
|     raise unittest.SkipTest('Django not found for testing') | ||||
| 
 | ||||
| from ...backend.django import JSONRPCAPI, api | ||||
| import json | ||||
| 
 | ||||
| 
 | ||||
| class TestDjangoBackend(TestCase): | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         os.environ['DJANGO_SETTINGS_MODULE'] = \ | ||||
|             'jsonrpc.tests.test_backend_django.settings' | ||||
|         super(TestDjangoBackend, cls).setUpClass() | ||||
| 
 | ||||
|     def test_urls(self): | ||||
|         self.assertTrue(isinstance(api.urls, list)) | ||||
|         for api_url in api.urls: | ||||
|             self.assertTrue(isinstance(api_url, RegexURLPattern)) | ||||
| 
 | ||||
|     def test_client(self): | ||||
|         @api.dispatcher.add_method | ||||
|         def dummy(request): | ||||
|             return "" | ||||
| 
 | ||||
|         json_data = { | ||||
|             "id": "0", | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "dummy", | ||||
|         } | ||||
|         response = self.client.post( | ||||
|             '', | ||||
|             json.dumps(json_data), | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = json.loads(response.content.decode('utf8')) | ||||
|         self.assertEqual(data['result'], '') | ||||
| 
 | ||||
|     def test_method_not_allowed(self): | ||||
|         response = self.client.get( | ||||
|             '', | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 405, "Should allow only POST") | ||||
| 
 | ||||
|     def test_invalid_request(self): | ||||
|         response = self.client.post( | ||||
|             '', | ||||
|             '{', | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = json.loads(response.content.decode('utf8')) | ||||
|         self.assertEqual(data['error']['code'], -32700) | ||||
|         self.assertEqual(data['error']['message'], 'Parse error') | ||||
| 
 | ||||
|     def test_resource_map(self): | ||||
|         response = self.client.get('/map') | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = response.content.decode('utf8') | ||||
|         self.assertIn("JSON-RPC map", data) | ||||
| 
 | ||||
|     def test_method_not_allowed_prefix(self): | ||||
|         response = self.client.get( | ||||
|             '/prefix/', | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 405) | ||||
| 
 | ||||
|     def test_resource_map_prefix(self): | ||||
|         response = self.client.get('/prefix/map') | ||||
|         self.assertEqual(response.status_code, 200) | ||||
| 
 | ||||
|     def test_empty_initial_dispatcher(self): | ||||
|         class SubDispatcher(type(api.dispatcher)): | ||||
|             pass | ||||
| 
 | ||||
|         custom_dispatcher = SubDispatcher() | ||||
|         custom_api = JSONRPCAPI(custom_dispatcher) | ||||
|         self.assertEqual(type(custom_api.dispatcher), SubDispatcher) | ||||
|         self.assertEqual(id(custom_api.dispatcher), id(custom_dispatcher)) | ||||
| @ -0,0 +1,7 @@ | ||||
| from django.conf.urls import url, include | ||||
| from jsonrpc.backend.django import api | ||||
| 
 | ||||
| urlpatterns = [ | ||||
|     url(r'', include(api.urls)), | ||||
|     url(r'^prefix/', include(api.urls)), | ||||
| ] | ||||
| @ -0,0 +1,182 @@ | ||||
| import json | ||||
| import sys | ||||
| 
 | ||||
| if sys.version_info < (3, 3): | ||||
|     from mock import patch | ||||
| else: | ||||
|     from unittest.mock import patch | ||||
| 
 | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| # Flask is supported only for python2 and python3.3+ | ||||
| if sys.version_info < (3, 0) or sys.version_info >= (3, 3): | ||||
|     try: | ||||
|         from flask import Flask | ||||
|     except ImportError: | ||||
|         raise unittest.SkipTest('Flask not found for testing') | ||||
| 
 | ||||
|     from ...backend.flask import JSONRPCAPI, api | ||||
| 
 | ||||
|     @api.dispatcher.add_method | ||||
|     def dummy(): | ||||
|         return "" | ||||
| 
 | ||||
| 
 | ||||
| @unittest.skipIf((3, 0) <= sys.version_info < (3, 3), | ||||
|                  'Flask does not support python 3.0 - 3.2') | ||||
| class TestFlaskBackend(unittest.TestCase): | ||||
|     REQUEST = json.dumps({ | ||||
|         "id": "0", | ||||
|         "jsonrpc": "2.0", | ||||
|         "method": "dummy", | ||||
|     }) | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         self.client = self._get_test_client(JSONRPCAPI()) | ||||
| 
 | ||||
|     def _get_test_client(self, api): | ||||
|         @api.dispatcher.add_method | ||||
|         def dummy(): | ||||
|             return "" | ||||
| 
 | ||||
|         app = Flask(__name__) | ||||
|         app.config["TESTING"] = True | ||||
|         app.register_blueprint(api.as_blueprint()) | ||||
|         return app.test_client() | ||||
| 
 | ||||
|     def test_client(self): | ||||
|         response = self.client.post( | ||||
|             '/', | ||||
|             data=self.REQUEST, | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = json.loads(response.data.decode('utf8')) | ||||
|         self.assertEqual(data['result'], '') | ||||
| 
 | ||||
|     def test_method_not_allowed(self): | ||||
|         response = self.client.get( | ||||
|             '/', | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 405, "Should allow only POST") | ||||
| 
 | ||||
|     def test_parse_error(self): | ||||
|         response = self.client.post( | ||||
|             '/', | ||||
|             data='{', | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = json.loads(response.data.decode('utf8')) | ||||
|         self.assertEqual(data['error']['code'], -32700) | ||||
|         self.assertEqual(data['error']['message'], 'Parse error') | ||||
| 
 | ||||
|     def test_wrong_content_type(self): | ||||
|         response = self.client.post( | ||||
|             '/', | ||||
|             data=self.REQUEST, | ||||
|             content_type='application/x-www-form-urlencoded', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = json.loads(response.data.decode('utf8')) | ||||
|         self.assertEqual(data['error']['code'], -32700) | ||||
|         self.assertEqual(data['error']['message'], 'Parse error') | ||||
| 
 | ||||
|     def test_invalid_request(self): | ||||
|         response = self.client.post( | ||||
|             '/', | ||||
|             data='{"method": "dummy", "id": 1}', | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = json.loads(response.data.decode('utf8')) | ||||
|         self.assertEqual(data['error']['code'], -32600) | ||||
|         self.assertEqual(data['error']['message'], 'Invalid Request') | ||||
| 
 | ||||
|     def test_method_not_found(self): | ||||
|         data = { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "dummy2", | ||||
|             "id": 1 | ||||
|         } | ||||
|         response = self.client.post( | ||||
|             '/', | ||||
|             data=json.dumps(data), | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = json.loads(response.data.decode('utf8')) | ||||
|         self.assertEqual(data['error']['code'], -32601) | ||||
|         self.assertEqual(data['error']['message'], 'Method not found') | ||||
| 
 | ||||
|     def test_invalid_parameters(self): | ||||
|         data = { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "dummy", | ||||
|             "params": [42], | ||||
|             "id": 1 | ||||
|         } | ||||
|         response = self.client.post( | ||||
|             '/', | ||||
|             data=json.dumps(data), | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = json.loads(response.data.decode('utf8')) | ||||
|         self.assertEqual(data['error']['code'], -32602) | ||||
|         self.assertEqual(data['error']['message'], 'Invalid params') | ||||
| 
 | ||||
|     def test_resource_map(self): | ||||
|         response = self.client.get('/map') | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertTrue("JSON-RPC map" in response.data.decode('utf8')) | ||||
| 
 | ||||
|     def test_method_not_allowed_prefix(self): | ||||
|         response = self.client.get( | ||||
|             '/', | ||||
|             content_type='application/json', | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 405) | ||||
| 
 | ||||
|     def test_resource_map_prefix(self): | ||||
|         response = self.client.get('/map') | ||||
|         self.assertEqual(response.status_code, 200) | ||||
| 
 | ||||
|     def test_as_view(self): | ||||
|         api = JSONRPCAPI() | ||||
|         with patch.object(api, 'jsonrpc') as mock_jsonrpc: | ||||
|             self.assertIs(api.as_view(), mock_jsonrpc) | ||||
| 
 | ||||
|     def test_not_check_content_type(self): | ||||
|         client = self._get_test_client(JSONRPCAPI(check_content_type=False)) | ||||
|         response = client.post( | ||||
|             '/', | ||||
|             data=self.REQUEST, | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = json.loads(response.data.decode('utf8')) | ||||
|         self.assertEqual(data['result'], '') | ||||
| 
 | ||||
|     def test_check_content_type(self): | ||||
|         client = self._get_test_client(JSONRPCAPI(check_content_type=False)) | ||||
|         response = client.post( | ||||
|             '/', | ||||
|             data=self.REQUEST, | ||||
|             content_type="application/x-www-form-urlencoded" | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         data = json.loads(response.data.decode('utf8')) | ||||
|         self.assertEqual(data['result'], '') | ||||
| 
 | ||||
|     def test_empty_initial_dispatcher(self): | ||||
|         class SubDispatcher(type(api.dispatcher)): | ||||
|             pass | ||||
| 
 | ||||
|         custom_dispatcher = SubDispatcher() | ||||
|         custom_api = JSONRPCAPI(custom_dispatcher) | ||||
|         self.assertEqual(type(custom_api.dispatcher), SubDispatcher) | ||||
|         self.assertEqual(id(custom_api.dispatcher), id(custom_dispatcher)) | ||||
| @ -0,0 +1,39 @@ | ||||
| """ Test base JSON-RPC classes.""" | ||||
| import sys | ||||
| 
 | ||||
| from ..base import JSONRPCBaseRequest, JSONRPCBaseResponse | ||||
| 
 | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCBaseRequest(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test JSONRPCBaseRequest functionality.""" | ||||
| 
 | ||||
|     def test_data(self): | ||||
|         request = JSONRPCBaseRequest() | ||||
|         self.assertEqual(request.data, {}) | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             request.data = [] | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             request.data = None | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCBaseResponse(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test JSONRPCBaseResponse functionality.""" | ||||
| 
 | ||||
|     def test_data(self): | ||||
|         response = JSONRPCBaseResponse(result="") | ||||
|         self.assertEqual(response.data, {}) | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             response.data = [] | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             response.data = None | ||||
| @ -0,0 +1,34 @@ | ||||
| """ Exmples of usage with tests. | ||||
| 
 | ||||
| Tests in this file represent examples taken from JSON-RPC specification. | ||||
| http://www.jsonrpc.org/specification#examples | ||||
| 
 | ||||
| """ | ||||
| import sys | ||||
| import json | ||||
| 
 | ||||
| from ..manager import JSONRPCResponseManager | ||||
| 
 | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| 
 | ||||
| def isjsonequal(json1, json2): | ||||
|     return json.loads(json1) == json.loads(json2) | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCExamples(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         self.dispatcher = { | ||||
|             "return_none": lambda: None, | ||||
|         } | ||||
| 
 | ||||
|     def test_none_as_result(self): | ||||
|         req = '{"jsonrpc": "2.0", "method": "return_none", "id": 0}' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "result": null, "id": 0}' | ||||
|         )) | ||||
| @ -0,0 +1,142 @@ | ||||
| from ..dispatcher import Dispatcher | ||||
| import sys | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| 
 | ||||
| class Math: | ||||
| 
 | ||||
|     def sum(self, a, b): | ||||
|         return a + b | ||||
| 
 | ||||
|     def diff(self, a, b): | ||||
|         return a - b | ||||
| 
 | ||||
| 
 | ||||
| class TestDispatcher(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test Dispatcher functionality.""" | ||||
| 
 | ||||
|     def test_getter(self): | ||||
|         d = Dispatcher() | ||||
| 
 | ||||
|         with self.assertRaises(KeyError): | ||||
|             d["method"] | ||||
| 
 | ||||
|         d["add"] = lambda *args: sum(args) | ||||
|         self.assertEqual(d["add"](1, 1), 2) | ||||
| 
 | ||||
|     def test_in(self): | ||||
|         d = Dispatcher() | ||||
|         d["method"] = lambda: "" | ||||
|         self.assertIn("method", d) | ||||
| 
 | ||||
|     def test_add_method(self): | ||||
|         d = Dispatcher() | ||||
| 
 | ||||
|         @d.add_method | ||||
|         def add(x, y): | ||||
|             return x + y | ||||
| 
 | ||||
|         self.assertIn("add", d) | ||||
|         self.assertEqual(d["add"](1, 1), 2) | ||||
| 
 | ||||
|     def test_add_method_with_name(self): | ||||
|         d = Dispatcher() | ||||
| 
 | ||||
|         @d.add_method(name="this.add") | ||||
|         def add(x, y): | ||||
|             return x + y | ||||
| 
 | ||||
|         self.assertNotIn("add", d) | ||||
|         self.assertIn("this.add", d) | ||||
|         self.assertEqual(d["this.add"](1, 1), 2) | ||||
| 
 | ||||
|     def test_add_class(self): | ||||
|         d = Dispatcher() | ||||
|         d.add_class(Math) | ||||
| 
 | ||||
|         self.assertIn("math.sum", d) | ||||
|         self.assertIn("math.diff", d) | ||||
|         self.assertEqual(d["math.sum"](3, 8), 11) | ||||
|         self.assertEqual(d["math.diff"](6, 9), -3) | ||||
| 
 | ||||
|     def test_add_object(self): | ||||
|         d = Dispatcher() | ||||
|         d.add_object(Math()) | ||||
| 
 | ||||
|         self.assertIn("math.sum", d) | ||||
|         self.assertIn("math.diff", d) | ||||
|         self.assertEqual(d["math.sum"](5, 2), 7) | ||||
|         self.assertEqual(d["math.diff"](15, 9), 6) | ||||
| 
 | ||||
|     def test_add_dict(self): | ||||
|         d = Dispatcher() | ||||
|         d.add_dict({"sum": lambda *args: sum(args)}, "util") | ||||
| 
 | ||||
|         self.assertIn("util.sum", d) | ||||
|         self.assertEqual(d["util.sum"](13, -2), 11) | ||||
| 
 | ||||
|     def test_add_method_keep_function_definitions(self): | ||||
| 
 | ||||
|         d = Dispatcher() | ||||
| 
 | ||||
|         @d.add_method | ||||
|         def one(x): | ||||
|             return x | ||||
| 
 | ||||
|         self.assertIsNotNone(one) | ||||
| 
 | ||||
|     def test_del_method(self): | ||||
|         d = Dispatcher() | ||||
|         d["method"] = lambda: "" | ||||
|         self.assertIn("method", d) | ||||
| 
 | ||||
|         del d["method"] | ||||
|         self.assertNotIn("method", d) | ||||
| 
 | ||||
|     def test_to_dict(self): | ||||
|         d = Dispatcher() | ||||
| 
 | ||||
|         def func(): | ||||
|             return "" | ||||
| 
 | ||||
|         d["method"] = func | ||||
|         self.assertEqual(dict(d), {"method": func}) | ||||
| 
 | ||||
|     def test_init_from_object_instance(self): | ||||
| 
 | ||||
|         class Dummy(): | ||||
| 
 | ||||
|             def one(self): | ||||
|                 pass | ||||
| 
 | ||||
|             def two(self): | ||||
|                 pass | ||||
| 
 | ||||
|         dummy = Dummy() | ||||
| 
 | ||||
|         d = Dispatcher(dummy) | ||||
| 
 | ||||
|         self.assertIn("one", d) | ||||
|         self.assertIn("two", d) | ||||
|         self.assertNotIn("__class__", d) | ||||
| 
 | ||||
|     def test_init_from_dictionary(self): | ||||
| 
 | ||||
|         dummy = { | ||||
|             'one': lambda x: x, | ||||
|             'two': lambda x: x, | ||||
|         } | ||||
| 
 | ||||
|         d = Dispatcher(dummy) | ||||
| 
 | ||||
|         self.assertIn("one", d) | ||||
|         self.assertIn("two", d) | ||||
| 
 | ||||
|     def test_dispatcher_representation(self): | ||||
| 
 | ||||
|         d = Dispatcher() | ||||
|         self.assertEqual('{}', repr(d)) | ||||
| @ -0,0 +1,206 @@ | ||||
| """ Exmples of usage with tests. | ||||
| 
 | ||||
| Tests in this file represent examples taken from JSON-RPC specification. | ||||
| http://www.jsonrpc.org/specification#examples | ||||
| 
 | ||||
| """ | ||||
| import sys | ||||
| import json | ||||
| 
 | ||||
| from ..manager import JSONRPCResponseManager | ||||
| from ..jsonrpc2 import JSONRPC20Request, JSONRPC20BatchRequest | ||||
| 
 | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| 
 | ||||
| def isjsonequal(json1, json2): | ||||
|     return json.loads(json1) == json.loads(json2) | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCExamples(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         self.dispatcher = { | ||||
|             "subtract": lambda a, b: a - b, | ||||
|         } | ||||
| 
 | ||||
|     def test_rpc_call_with_positional_parameters(self): | ||||
|         req = '{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}'  # noqa | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "result": 19, "id": 1}' | ||||
|         )) | ||||
| 
 | ||||
|         req = '{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}'  # noqa | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "result": -19, "id": 2}' | ||||
|         )) | ||||
| 
 | ||||
|     def test_rpc_call_with_named_parameters(self): | ||||
|         def subtract(minuend=None, subtrahend=None): | ||||
|             return minuend - subtrahend | ||||
| 
 | ||||
|         dispatcher = { | ||||
|             "subtract": subtract, | ||||
|             "sum": lambda *args: sum(args), | ||||
|             "get_data": lambda: ["hello", 5], | ||||
|         } | ||||
| 
 | ||||
|         req = '{"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}'  # noqa | ||||
|         response = JSONRPCResponseManager.handle(req, dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "result": 19, "id": 3}' | ||||
|         )) | ||||
| 
 | ||||
|         req = '{"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4}'  # noqa | ||||
|         response = JSONRPCResponseManager.handle(req, dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "result": 19, "id": 4}', | ||||
|         )) | ||||
| 
 | ||||
|     def test_notification(self): | ||||
|         req = '{"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertEqual(response, None) | ||||
| 
 | ||||
|         req = '{"jsonrpc": "2.0", "method": "foobar"}' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertEqual(response, None) | ||||
| 
 | ||||
|     def test_rpc_call_of_non_existent_method(self): | ||||
|         req = '{"jsonrpc": "2.0", "method": "foobar", "id": "1"}' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"}'  # noqa | ||||
|         )) | ||||
| 
 | ||||
|     def test_rpc_call_with_invalid_json(self): | ||||
|         req = '{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}'  # noqa | ||||
|         )) | ||||
| 
 | ||||
|     def test_rpc_call_with_invalid_request_object(self): | ||||
|         req = '{"jsonrpc": "2.0", "method": 1, "params": "bar"}' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}'  # noqa | ||||
|         )) | ||||
| 
 | ||||
|     def test_rpc_call_batch_invalid_json(self): | ||||
|         req = """[ | ||||
|             {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, | ||||
|             {"jsonrpc": "2.0", "method" | ||||
|         ]""" | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}'  # noqa | ||||
|         )) | ||||
| 
 | ||||
|     def test_rpc_call_with_an_empty_array(self): | ||||
|         req = '[]' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}'  # noqa | ||||
|         )) | ||||
| 
 | ||||
|     def test_rpc_call_with_rpc_call_with_an_invalid_batch_but_not_empty(self): | ||||
|         req = '[1]' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.json, | ||||
|             '{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}'  # noqa | ||||
|         )) | ||||
| 
 | ||||
|     def test_rpc_call_with_invalid_batch(self): | ||||
|         req = '[1,2,3]' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue( | ||||
|             response, | ||||
|             json.loads("""[ | ||||
|                 {"jsonrpc": "2.0", "error": {"code": -32600, | ||||
|                 "message": "Invalid Request"}, "id": null}, | ||||
|                 {"jsonrpc": "2.0", "error": {"code": -32600, | ||||
|                 "message": "Invalid Request"}, "id": null}, | ||||
|                 {"jsonrpc": "2.0", "error": {"code": -32600, | ||||
|                 "message": "Invalid Request"}, "id": null} | ||||
|             ]""") | ||||
|         ) | ||||
| 
 | ||||
|     def test_rpc_call_batch(self): | ||||
|         req = """[ | ||||
|             {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, | ||||
|             {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, | ||||
|             {"jsonrpc": "2.0", "method": "subtract", | ||||
|             "params": [42,23], "id": "2"}, | ||||
|             {"foo": "boo"}, | ||||
|             {"jsonrpc": "2.0", "method": "foo.get", | ||||
|             "params": {"name": "myself"}, "id": "5"}, | ||||
|             {"jsonrpc": "2.0", "method": "get_data", "id": "9"} | ||||
|         ]""" | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue( | ||||
|             response, | ||||
|             json.loads("""[ | ||||
|                 {"jsonrpc": "2.0", "result": 7, "id": "1"}, | ||||
|                 {"jsonrpc": "2.0", "result": 19, "id": "2"}, | ||||
|                 {"jsonrpc": "2.0", "error": {"code": -32600, | ||||
|                 "message": "Invalid Request"}, "id": null}, | ||||
|                 {"jsonrpc": "2.0", "error": {"code": -32601, | ||||
|                 "message": "Method not found"}, "id": "5"}, | ||||
|                 {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"} | ||||
|             ]""") | ||||
|         ) | ||||
| 
 | ||||
|     def test_rpc_call_batch_all_notifications(self): | ||||
|         req = """[ | ||||
|             {"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]}, | ||||
|             {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]} | ||||
|         ]""" | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertEqual(response, None) | ||||
| 
 | ||||
|     def test_rpc_call_response_request(self): | ||||
|         req = '{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}'  # noqa | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isinstance( | ||||
|             response.request, | ||||
|             JSONRPC20Request | ||||
|         )) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.request.json, | ||||
|             req | ||||
|         )) | ||||
| 
 | ||||
|     def test_rpc_call_response_request_batch(self): | ||||
|         req = """[ | ||||
|             {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, | ||||
|             {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, | ||||
|             {"jsonrpc": "2.0", "method": "subtract", | ||||
|             "params": [42,23], "id": "2"}, | ||||
|             {"jsonrpc": "2.0", "method": "foo.get", | ||||
|             "params": {"name": "myself"}, "id": "5"}, | ||||
|             {"jsonrpc": "2.0", "method": "get_data", "id": "9"} | ||||
|         ]""" | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isinstance( | ||||
|             response.request, | ||||
|             JSONRPC20BatchRequest | ||||
|         )) | ||||
|         self.assertTrue(isjsonequal( | ||||
|             response.request.json, | ||||
|             req | ||||
|         )) | ||||
| @ -0,0 +1 @@ | ||||
| """ Tets base JSON-RPC structures.""" | ||||
| @ -0,0 +1,429 @@ | ||||
| import json | ||||
| import sys | ||||
| 
 | ||||
| from ..exceptions import JSONRPCInvalidRequestException | ||||
| from ..jsonrpc1 import ( | ||||
|     JSONRPC10Request, | ||||
|     JSONRPC10Response, | ||||
| ) | ||||
| 
 | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPC10Request(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test JSONRPC10Request functionality.""" | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         self.request_params = { | ||||
|             "method": "add", | ||||
|             "params": [1, 2], | ||||
|             "_id": 1, | ||||
|         } | ||||
| 
 | ||||
|     def test_correct_init(self): | ||||
|         """ Test object is created.""" | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_validation_incorrect_no_parameters(self): | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Request() | ||||
| 
 | ||||
|     def test_method_validation_str(self): | ||||
|         self.request_params.update({"method": "add"}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_method_validation_not_str(self): | ||||
|         self.request_params.update({"method": []}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"method": {}}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"method": None}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_params_validation_list(self): | ||||
|         self.request_params.update({"params": []}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"params": [0]}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_params_validation_tuple(self): | ||||
|         self.request_params.update({"params": ()}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"params": tuple([0])}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_params_validation_dict(self): | ||||
|         self.request_params.update({"params": {}}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"params": {"a": 0}}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_params_validation_none(self): | ||||
|         self.request_params.update({"params": None}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_params_validation_incorrect(self): | ||||
|         self.request_params.update({"params": "str"}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_request_args(self): | ||||
|         self.assertEqual(JSONRPC10Request("add", []).args, ()) | ||||
|         self.assertEqual(JSONRPC10Request("add", [1, 2]).args, (1, 2)) | ||||
| 
 | ||||
|     def test_id_validation_string(self): | ||||
|         self.request_params.update({"_id": "id"}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_int(self): | ||||
|         self.request_params.update({"_id": 0}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_null(self): | ||||
|         self.request_params.update({"_id": "null"}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_none(self): | ||||
|         self.request_params.update({"_id": None}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_float(self): | ||||
|         self.request_params.update({"_id": 0.1}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_list_tuple(self): | ||||
|         self.request_params.update({"_id": []}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"_id": ()}) | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_default_id_none(self): | ||||
|         del self.request_params["_id"] | ||||
|         JSONRPC10Request(**self.request_params) | ||||
| 
 | ||||
|     def test_data_method_1(self): | ||||
|         r = JSONRPC10Request("add", []) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_method_2(self): | ||||
|         r = JSONRPC10Request(method="add", params=[]) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_params_1(self): | ||||
|         r = JSONRPC10Request("add", params=[], _id=None) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_params_2(self): | ||||
|         r = JSONRPC10Request("add", ()) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_params_3(self): | ||||
|         r = JSONRPC10Request("add", (1, 2)) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [1, 2], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_1(self): | ||||
|         r = JSONRPC10Request("add", [], _id="null") | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": "null", | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_1_notification(self): | ||||
|         r = JSONRPC10Request("add", [], _id="null", is_notification=True) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_2(self): | ||||
|         r = JSONRPC10Request("add", [], _id=None) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_2_notification(self): | ||||
|         r = JSONRPC10Request("add", [], _id=None, is_notification=True) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_3(self): | ||||
|         r = JSONRPC10Request("add", [], _id="id") | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": "id", | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_3_notification(self): | ||||
|         r = JSONRPC10Request("add", [], _id="id", is_notification=True) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_4(self): | ||||
|         r = JSONRPC10Request("add", [], _id=0) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": 0, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_4_notification(self): | ||||
|         r = JSONRPC10Request("add", [], _id=0, is_notification=True) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_is_notification(self): | ||||
|         r = JSONRPC10Request("add", []) | ||||
|         self.assertTrue(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC10Request("add", [], _id=None) | ||||
|         self.assertTrue(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC10Request("add", [], _id="null") | ||||
|         self.assertFalse(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC10Request("add", [], _id=0) | ||||
|         self.assertFalse(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC10Request("add", [], is_notification=True) | ||||
|         self.assertTrue(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC10Request("add", [], is_notification=True, _id=None) | ||||
|         self.assertTrue(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC10Request("add", [], is_notification=True, _id=0) | ||||
|         self.assertTrue(r.is_notification) | ||||
| 
 | ||||
|     def test_set_unset_notification_keep_id(self): | ||||
|         r = JSONRPC10Request("add", [], is_notification=True, _id=0) | ||||
|         self.assertTrue(r.is_notification) | ||||
|         self.assertEqual(r.data["id"], None) | ||||
| 
 | ||||
|         r.is_notification = False | ||||
|         self.assertFalse(r.is_notification) | ||||
|         self.assertEqual(r.data["id"], 0) | ||||
| 
 | ||||
|     def test_error_if_notification_true_but_id_none(self): | ||||
|         r = JSONRPC10Request("add", [], is_notification=True, _id=None) | ||||
|         with self.assertRaises(ValueError): | ||||
|             r.is_notification = False | ||||
| 
 | ||||
|     def test_from_json_invalid_request_method(self): | ||||
|         str_json = json.dumps({ | ||||
|             "params": [1, 2], | ||||
|             "id": 0, | ||||
|         }) | ||||
| 
 | ||||
|         with self.assertRaises(JSONRPCInvalidRequestException): | ||||
|             JSONRPC10Request.from_json(str_json) | ||||
| 
 | ||||
|     def test_from_json_invalid_request_params(self): | ||||
|         str_json = json.dumps({ | ||||
|             "method": "add", | ||||
|             "id": 0, | ||||
|         }) | ||||
| 
 | ||||
|         with self.assertRaises(JSONRPCInvalidRequestException): | ||||
|             JSONRPC10Request.from_json(str_json) | ||||
| 
 | ||||
|     def test_from_json_invalid_request_id(self): | ||||
|         str_json = json.dumps({ | ||||
|             "method": "add", | ||||
|             "params": [1, 2], | ||||
|         }) | ||||
| 
 | ||||
|         with self.assertRaises(JSONRPCInvalidRequestException): | ||||
|             JSONRPC10Request.from_json(str_json) | ||||
| 
 | ||||
|     def test_from_json_invalid_request_extra_data(self): | ||||
|         str_json = json.dumps({ | ||||
|             "method": "add", | ||||
|             "params": [1, 2], | ||||
|             "id": 0, | ||||
|             "is_notification": True, | ||||
|         }) | ||||
| 
 | ||||
|         with self.assertRaises(JSONRPCInvalidRequestException): | ||||
|             JSONRPC10Request.from_json(str_json) | ||||
| 
 | ||||
|     def test_from_json_request(self): | ||||
|         str_json = json.dumps({ | ||||
|             "method": "add", | ||||
|             "params": [1, 2], | ||||
|             "id": 0, | ||||
|         }) | ||||
| 
 | ||||
|         request = JSONRPC10Request.from_json(str_json) | ||||
|         self.assertTrue(isinstance(request, JSONRPC10Request)) | ||||
|         self.assertEqual(request.method, "add") | ||||
|         self.assertEqual(request.params, [1, 2]) | ||||
|         self.assertEqual(request._id, 0) | ||||
|         self.assertFalse(request.is_notification) | ||||
| 
 | ||||
|     def test_from_json_request_notification(self): | ||||
|         str_json = json.dumps({ | ||||
|             "method": "add", | ||||
|             "params": [1, 2], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|         request = JSONRPC10Request.from_json(str_json) | ||||
|         self.assertTrue(isinstance(request, JSONRPC10Request)) | ||||
|         self.assertEqual(request.method, "add") | ||||
|         self.assertEqual(request.params, [1, 2]) | ||||
|         self.assertEqual(request._id, None) | ||||
|         self.assertTrue(request.is_notification) | ||||
| 
 | ||||
|     def test_from_json_string_not_dict(self): | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Request.from_json("[]") | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Request.from_json("0") | ||||
| 
 | ||||
|     def test_data_setter(self): | ||||
|         request = JSONRPC10Request(**self.request_params) | ||||
|         with self.assertRaises(ValueError): | ||||
|             request.data = [] | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             request.data = "" | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             request.data = None | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPC10Response(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test JSONRPC10Response functionality.""" | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         self.response_success_params = { | ||||
|             "result": "", | ||||
|             "error": None, | ||||
|             "_id": 1, | ||||
|         } | ||||
|         self.response_error_params = { | ||||
|             "result": None, | ||||
|             "error": { | ||||
|                 "code": 1, | ||||
|                 "message": "error", | ||||
|             }, | ||||
|             "_id": 1, | ||||
|         } | ||||
| 
 | ||||
|     def test_correct_init(self): | ||||
|         """ Test object is created.""" | ||||
|         JSONRPC10Response(**self.response_success_params) | ||||
|         JSONRPC10Response(**self.response_error_params) | ||||
| 
 | ||||
|     def test_validation_incorrect_no_parameters(self): | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Response() | ||||
| 
 | ||||
|     def test_validation_success_incorrect(self): | ||||
|         wrong_params = self.response_success_params | ||||
|         del wrong_params["_id"] | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Response(**wrong_params) | ||||
| 
 | ||||
|     def test_validation_error_incorrect(self): | ||||
|         wrong_params = self.response_error_params | ||||
|         del wrong_params["_id"] | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Response(**wrong_params) | ||||
| 
 | ||||
|     def _test_validation_incorrect_result_and_error(self): | ||||
|         # @todo: remove | ||||
|         # It is OK because result is an mepty string, it is still result | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC10Response(result="", error="", _id=0) | ||||
| 
 | ||||
|         response = JSONRPC10Response(error="", _id=0) | ||||
|         with self.assertRaises(ValueError): | ||||
|             response.result = "" | ||||
| 
 | ||||
|     def test_data(self): | ||||
|         r = JSONRPC10Response(result="", _id=0) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "result": "", | ||||
|             "id": 0, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_setter(self): | ||||
|         response = JSONRPC10Response(**self.response_success_params) | ||||
|         with self.assertRaises(ValueError): | ||||
|             response.data = [] | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             response.data = "" | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             response.data = None | ||||
| 
 | ||||
|     def test_validation_id(self): | ||||
|         response = JSONRPC10Response(**self.response_success_params) | ||||
|         self.assertEqual(response._id, self.response_success_params["_id"]) | ||||
| @ -0,0 +1,728 @@ | ||||
| import json | ||||
| import sys | ||||
| 
 | ||||
| from ..exceptions import JSONRPCInvalidRequestException | ||||
| from ..jsonrpc2 import ( | ||||
|     JSONRPC20Request, | ||||
|     JSONRPC20BatchRequest, | ||||
|     JSONRPC20Response, | ||||
|     JSONRPC20BatchResponse, | ||||
| ) | ||||
| 
 | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPC20Request(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test JSONRPC20Request functionality.""" | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         self.request_params = { | ||||
|             "method": "add", | ||||
|             "params": [1, 2], | ||||
|             "_id": 1, | ||||
|         } | ||||
| 
 | ||||
|     def test_correct_init(self): | ||||
|         """ Test object is created.""" | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_validation_incorrect_no_parameters(self): | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Request() | ||||
| 
 | ||||
|     def test_method_validation_str(self): | ||||
|         self.request_params.update({"method": "add"}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_method_validation_not_str(self): | ||||
|         self.request_params.update({"method": []}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"method": {}}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_method_validation_str_rpc_prefix(self): | ||||
|         """ Test method SHOULD NOT starts with rpc. """ | ||||
|         self.request_params.update({"method": "rpc."}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"method": "rpc.test"}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"method": "rpccorrect"}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"method": "rpc"}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_params_validation_list(self): | ||||
|         self.request_params.update({"params": []}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"params": [0]}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_params_validation_tuple(self): | ||||
|         self.request_params.update({"params": ()}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"params": tuple([0])}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_params_validation_dict(self): | ||||
|         self.request_params.update({"params": {}}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"params": {"a": 0}}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_params_validation_none(self): | ||||
|         self.request_params.update({"params": None}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_params_validation_incorrect(self): | ||||
|         self.request_params.update({"params": "str"}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_request_args(self): | ||||
|         self.assertEqual(JSONRPC20Request("add").args, ()) | ||||
|         self.assertEqual(JSONRPC20Request("add", []).args, ()) | ||||
|         self.assertEqual(JSONRPC20Request("add", {"a": 1}).args, ()) | ||||
|         self.assertEqual(JSONRPC20Request("add", [1, 2]).args, (1, 2)) | ||||
| 
 | ||||
|     def test_request_kwargs(self): | ||||
|         self.assertEqual(JSONRPC20Request("add").kwargs, {}) | ||||
|         self.assertEqual(JSONRPC20Request("add", [1, 2]).kwargs, {}) | ||||
|         self.assertEqual(JSONRPC20Request("add", {}).kwargs, {}) | ||||
|         self.assertEqual(JSONRPC20Request("add", {"a": 1}).kwargs, {"a": 1}) | ||||
| 
 | ||||
|     def test_id_validation_string(self): | ||||
|         self.request_params.update({"_id": "id"}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_int(self): | ||||
|         self.request_params.update({"_id": 0}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_null(self): | ||||
|         self.request_params.update({"_id": "null"}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_none(self): | ||||
|         self.request_params.update({"_id": None}) | ||||
|         JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_float(self): | ||||
|         self.request_params.update({"_id": 0.1}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_id_validation_incorrect(self): | ||||
|         self.request_params.update({"_id": []}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|         self.request_params.update({"_id": ()}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Request(**self.request_params) | ||||
| 
 | ||||
|     def test_data_method_1(self): | ||||
|         r = JSONRPC20Request("add") | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_method_2(self): | ||||
|         r = JSONRPC20Request(method="add") | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_method_3(self): | ||||
|         r = JSONRPC20Request("add", None) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_params_1(self): | ||||
|         r = JSONRPC20Request("add", params=None, _id=None) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_params_2(self): | ||||
|         r = JSONRPC20Request("add", []) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_params_3(self): | ||||
|         r = JSONRPC20Request("add", ()) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_params_4(self): | ||||
|         r = JSONRPC20Request("add", (1, 2)) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "params": [1, 2], | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_params_5(self): | ||||
|         r = JSONRPC20Request("add", {"a": 0}) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "params": {"a": 0}, | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_1(self): | ||||
|         r = JSONRPC20Request("add", _id="null") | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": "null", | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_1_notification(self): | ||||
|         r = JSONRPC20Request("add", _id="null", is_notification=True) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_2(self): | ||||
|         r = JSONRPC20Request("add", _id=None) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_2_notification(self): | ||||
|         r = JSONRPC20Request("add", _id=None, is_notification=True) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_3(self): | ||||
|         r = JSONRPC20Request("add", _id="id") | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": "id", | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_3_notification(self): | ||||
|         r = JSONRPC20Request("add", _id="id", is_notification=True) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_4(self): | ||||
|         r = JSONRPC20Request("add", _id=0) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": 0, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_id_4_notification(self): | ||||
|         r = JSONRPC20Request("add", _id=0, is_notification=True) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|         }) | ||||
| 
 | ||||
|     def test_is_notification(self): | ||||
|         r = JSONRPC20Request("add") | ||||
|         self.assertFalse(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC20Request("add", _id=None) | ||||
|         self.assertFalse(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC20Request("add", _id="null") | ||||
|         self.assertFalse(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC20Request("add", _id=0) | ||||
|         self.assertFalse(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC20Request("add", is_notification=True) | ||||
|         self.assertTrue(r.is_notification) | ||||
| 
 | ||||
|         r = JSONRPC20Request("add", is_notification=True, _id=None) | ||||
|         self.assertTrue(r.is_notification) | ||||
|         self.assertNotIn("id", r.data) | ||||
| 
 | ||||
|         r = JSONRPC20Request("add", is_notification=True, _id=0) | ||||
|         self.assertTrue(r.is_notification) | ||||
|         self.assertNotIn("id", r.data) | ||||
| 
 | ||||
|     def test_set_unset_notification_keep_id(self): | ||||
|         r = JSONRPC20Request("add", is_notification=True, _id=0) | ||||
|         self.assertTrue(r.is_notification) | ||||
|         self.assertFalse("id" in r.data) | ||||
| 
 | ||||
|         r.is_notification = False | ||||
|         self.assertFalse(r.is_notification) | ||||
|         self.assertTrue("id" in r.data) | ||||
|         self.assertEqual(r.data["id"], 0) | ||||
| 
 | ||||
|     def test_serialize_method_1(self): | ||||
|         r = JSONRPC20Request("add") | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": None, | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_method_2(self): | ||||
|         r = JSONRPC20Request(method="add") | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": None, | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_method_3(self): | ||||
|         r = JSONRPC20Request("add", None) | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": None, | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_params_1(self): | ||||
|         r = JSONRPC20Request("add", params=None, _id=None) | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": None, | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_params_2(self): | ||||
|         r = JSONRPC20Request("add", []) | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_params_3(self): | ||||
|         r = JSONRPC20Request("add", ()) | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "params": [], | ||||
|             "id": None, | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_params_4(self): | ||||
|         r = JSONRPC20Request("add", (1, 2)) | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "params": [1, 2], | ||||
|             "id": None, | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_params_5(self): | ||||
|         r = JSONRPC20Request("add", {"a": 0}) | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "params": {"a": 0}, | ||||
|             "id": None, | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_id_1(self): | ||||
|         r = JSONRPC20Request("add", _id="null") | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": "null", | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_id_2(self): | ||||
|         r = JSONRPC20Request("add", _id=None) | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": None, | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_id_3(self): | ||||
|         r = JSONRPC20Request("add", _id="id") | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": "id", | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_serialize_id_4(self): | ||||
|         r = JSONRPC20Request("add", _id=0) | ||||
|         self.assertTrue({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "id": 0, | ||||
|         }, json.loads(r.json)) | ||||
| 
 | ||||
|     def test_from_json_request_no_id(self): | ||||
|         str_json = json.dumps({ | ||||
|             "method": "add", | ||||
|             "params": [1, 2], | ||||
|             "jsonrpc": "2.0", | ||||
|         }) | ||||
| 
 | ||||
|         request = JSONRPC20Request.from_json(str_json) | ||||
|         self.assertTrue(isinstance(request, JSONRPC20Request)) | ||||
|         self.assertEqual(request.method, "add") | ||||
|         self.assertEqual(request.params, [1, 2]) | ||||
|         self.assertEqual(request._id, None) | ||||
|         self.assertTrue(request.is_notification) | ||||
| 
 | ||||
|     def test_from_json_request_no_params(self): | ||||
|         str_json = json.dumps({ | ||||
|             "method": "add", | ||||
|             "jsonrpc": "2.0", | ||||
|         }) | ||||
| 
 | ||||
|         request = JSONRPC20Request.from_json(str_json) | ||||
|         self.assertTrue(isinstance(request, JSONRPC20Request)) | ||||
|         self.assertEqual(request.method, "add") | ||||
|         self.assertEqual(request.params, None) | ||||
|         self.assertEqual(request._id, None) | ||||
|         self.assertTrue(request.is_notification) | ||||
| 
 | ||||
|     def test_from_json_request_null_id(self): | ||||
|         str_json = json.dumps({ | ||||
|             "method": "add", | ||||
|             "jsonrpc": "2.0", | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|         request = JSONRPC20Request.from_json(str_json) | ||||
|         self.assertTrue(isinstance(request, JSONRPC20Request)) | ||||
|         self.assertEqual(request.method, "add") | ||||
|         self.assertEqual(request.params, None) | ||||
|         self.assertEqual(request._id, None) | ||||
|         self.assertFalse(request.is_notification) | ||||
| 
 | ||||
|     def test_from_json_request(self): | ||||
|         str_json = json.dumps({ | ||||
|             "method": "add", | ||||
|             "params": [0, 1], | ||||
|             "jsonrpc": "2.0", | ||||
|             "id": "id", | ||||
|         }) | ||||
| 
 | ||||
|         request = JSONRPC20Request.from_json(str_json) | ||||
|         self.assertTrue(isinstance(request, JSONRPC20Request)) | ||||
|         self.assertEqual(request.method, "add") | ||||
|         self.assertEqual(request.params, [0, 1]) | ||||
|         self.assertEqual(request._id, "id") | ||||
|         self.assertFalse(request.is_notification) | ||||
| 
 | ||||
|     def test_from_json_invalid_request_jsonrpc(self): | ||||
|         str_json = json.dumps({ | ||||
|             "method": "add", | ||||
|         }) | ||||
| 
 | ||||
|         with self.assertRaises(JSONRPCInvalidRequestException): | ||||
|             JSONRPC20Request.from_json(str_json) | ||||
| 
 | ||||
|     def test_from_json_invalid_request_method(self): | ||||
|         str_json = json.dumps({ | ||||
|             "jsonrpc": "2.0", | ||||
|         }) | ||||
| 
 | ||||
|         with self.assertRaises(JSONRPCInvalidRequestException): | ||||
|             JSONRPC20Request.from_json(str_json) | ||||
| 
 | ||||
|     def test_from_json_invalid_request_extra_data(self): | ||||
|         str_json = json.dumps({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "add", | ||||
|             "is_notification": True, | ||||
|         }) | ||||
| 
 | ||||
|         with self.assertRaises(JSONRPCInvalidRequestException): | ||||
|             JSONRPC20Request.from_json(str_json) | ||||
| 
 | ||||
|     def test_data_setter(self): | ||||
|         request = JSONRPC20Request(**self.request_params) | ||||
|         with self.assertRaises(ValueError): | ||||
|             request.data = [] | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             request.data = "" | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             request.data = None | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPC20BatchRequest(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test JSONRPC20BatchRequest functionality.""" | ||||
| 
 | ||||
|     def test_batch_request(self): | ||||
|         request = JSONRPC20BatchRequest( | ||||
|             JSONRPC20Request("devide", {"num": 1, "denom": 2}, _id=1), | ||||
|             JSONRPC20Request("devide", {"num": 3, "denom": 2}, _id=2), | ||||
|         ) | ||||
|         self.assertEqual(json.loads(request.json), [ | ||||
|             {"method": "devide", "params": {"num": 1, "denom": 2}, "id": 1, | ||||
|              "jsonrpc": "2.0"}, | ||||
|             {"method": "devide", "params": {"num": 3, "denom": 2}, "id": 2, | ||||
|              "jsonrpc": "2.0"}, | ||||
|         ]) | ||||
| 
 | ||||
|     def test_from_json_batch(self): | ||||
|         str_json = json.dumps([ | ||||
|             {"method": "add", "params": [1, 2], "jsonrpc": "2.0"}, | ||||
|             {"method": "mul", "params": [1, 2], "jsonrpc": "2.0"}, | ||||
|         ]) | ||||
| 
 | ||||
|         requests = JSONRPC20BatchRequest.from_json(str_json) | ||||
|         self.assertTrue(isinstance(requests, JSONRPC20BatchRequest)) | ||||
|         for r in requests: | ||||
|             self.assertTrue(isinstance(r, JSONRPC20Request)) | ||||
|             self.assertTrue(r.method in ["add", "mul"]) | ||||
|             self.assertEqual(r.params, [1, 2]) | ||||
|             self.assertEqual(r._id, None) | ||||
|             self.assertTrue(r.is_notification) | ||||
| 
 | ||||
|     def test_from_json_batch_one(self): | ||||
|         str_json = json.dumps([ | ||||
|             {"method": "add", "params": [1, 2], "jsonrpc": "2.0", "id": None}, | ||||
|         ]) | ||||
| 
 | ||||
|         requests = JSONRPC20Request.from_json(str_json) | ||||
|         self.assertTrue(isinstance(requests, JSONRPC20BatchRequest)) | ||||
|         requests = list(requests) | ||||
|         self.assertEqual(len(requests), 1) | ||||
|         r = requests[0] | ||||
|         self.assertTrue(isinstance(r, JSONRPC20Request)) | ||||
|         self.assertEqual(r.method, "add") | ||||
|         self.assertEqual(r.params, [1, 2]) | ||||
|         self.assertEqual(r._id, None) | ||||
|         self.assertFalse(r.is_notification) | ||||
| 
 | ||||
|     def test_response_iterator(self): | ||||
|         requests = JSONRPC20BatchRequest( | ||||
|             JSONRPC20Request("devide", {"num": 1, "denom": 2}, _id=1), | ||||
|             JSONRPC20Request("devide", {"num": 3, "denom": 2}, _id=2), | ||||
|         ) | ||||
|         for request in requests: | ||||
|             self.assertTrue(isinstance(request, JSONRPC20Request)) | ||||
|             self.assertEqual(request.method, "devide") | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPC20Response(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test JSONRPC20Response functionality.""" | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         self.response_success_params = { | ||||
|             "result": "", | ||||
|             "_id": 1, | ||||
|         } | ||||
|         self.response_error_params = { | ||||
|             "error": { | ||||
|                 "code": 1, | ||||
|                 "message": "error", | ||||
|             }, | ||||
|             "_id": 1, | ||||
|         } | ||||
| 
 | ||||
|     def test_correct_init(self): | ||||
|         """ Test object is created.""" | ||||
|         JSONRPC20Response(**self.response_success_params) | ||||
| 
 | ||||
|     def test_validation_incorrect_no_parameters(self): | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Response() | ||||
| 
 | ||||
|     def test_validation_incorrect_result_and_error(self): | ||||
|         response = JSONRPC20Response(error={"code": 1, "message": ""}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             response.result = "" | ||||
| 
 | ||||
|     def test_validation_error_correct(self): | ||||
|         JSONRPC20Response(**self.response_error_params) | ||||
| 
 | ||||
|     def test_validation_error_incorrect(self): | ||||
|         self.response_error_params["error"].update({"code": "str"}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Response(**self.response_error_params) | ||||
| 
 | ||||
|     def test_validation_error_incorrect_no_code(self): | ||||
|         del self.response_error_params["error"]["code"] | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Response(**self.response_error_params) | ||||
| 
 | ||||
|     def test_validation_error_incorrect_no_message(self): | ||||
|         del self.response_error_params["error"]["message"] | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Response(**self.response_error_params) | ||||
| 
 | ||||
|     def test_validation_error_incorrect_message_not_str(self): | ||||
|         self.response_error_params["error"].update({"message": 0}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPC20Response(**self.response_error_params) | ||||
| 
 | ||||
|     def test_validation_id(self): | ||||
|         response = JSONRPC20Response(**self.response_success_params) | ||||
|         self.assertEqual(response._id, self.response_success_params["_id"]) | ||||
| 
 | ||||
|     def test_validation_id_incorrect_type(self): | ||||
|         response = JSONRPC20Response(**self.response_success_params) | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             response._id = [] | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             response._id = {} | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             response._id = 0.1 | ||||
| 
 | ||||
|     def test_data_result(self): | ||||
|         r = JSONRPC20Response(result="") | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "result": "", | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_result_id_none(self): | ||||
|         r = JSONRPC20Response(result="", _id=None) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "result": "", | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_result_id(self): | ||||
|         r = JSONRPC20Response(result="", _id=0) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "result": "", | ||||
|             "id": 0, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_error(self): | ||||
|         r = JSONRPC20Response(error={"code": 0, "message": ""}) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "error": { | ||||
|                 "code": 0, | ||||
|                 "message": "", | ||||
|             }, | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_error_id_none(self): | ||||
|         r = JSONRPC20Response(error={"code": 0, "message": ""}, _id=None) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "error": { | ||||
|                 "code": 0, | ||||
|                 "message": "", | ||||
|             }, | ||||
|             "id": None, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_error_id(self): | ||||
|         r = JSONRPC20Response(error={"code": 0, "message": ""}, _id=0) | ||||
|         self.assertEqual(json.loads(r.json), r.data) | ||||
|         self.assertEqual(r.data, { | ||||
|             "jsonrpc": "2.0", | ||||
|             "error": { | ||||
|                 "code": 0, | ||||
|                 "message": "", | ||||
|             }, | ||||
|             "id": 0, | ||||
|         }) | ||||
| 
 | ||||
|     def test_data_setter(self): | ||||
|         response = JSONRPC20Response(**self.response_success_params) | ||||
|         with self.assertRaises(ValueError): | ||||
|             response.data = [] | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             response.data = "" | ||||
| 
 | ||||
|         with self.assertRaises(ValueError): | ||||
|             response.data = None | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPC20BatchResponse(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test JSONRPC20BatchResponse functionality.""" | ||||
| 
 | ||||
|     def test_batch_response(self): | ||||
|         response = JSONRPC20BatchResponse( | ||||
|             JSONRPC20Response(result="result", _id=1), | ||||
|             JSONRPC20Response(error={"code": 0, "message": ""}, _id=2), | ||||
|         ) | ||||
|         self.assertEqual(json.loads(response.json), [ | ||||
|             {"result": "result", "id": 1, "jsonrpc": "2.0"}, | ||||
|             {"error": {"code": 0, "message": ""}, "id": 2, "jsonrpc": "2.0"}, | ||||
|         ]) | ||||
| 
 | ||||
|     def test_response_iterator(self): | ||||
|         responses = JSONRPC20BatchResponse( | ||||
|             JSONRPC20Response(result="result", _id=1), | ||||
|             JSONRPC20Response(result="result", _id=2), | ||||
|         ) | ||||
|         for response in responses: | ||||
|             self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|             self.assertEqual(response.result, "result") | ||||
| 
 | ||||
|     def test_batch_response_data(self): | ||||
|         response = JSONRPC20BatchResponse( | ||||
|             JSONRPC20Response(result="result", _id=1), | ||||
|             JSONRPC20Response(result="result", _id=2), | ||||
|             JSONRPC20Response(result="result"), | ||||
|         ) | ||||
|         self.assertEqual(response.data, [ | ||||
|             {"id": 1, "jsonrpc": "2.0", "result": "result"}, | ||||
|             {"id": 2, "jsonrpc": "2.0", "result": "result"}, | ||||
|             {"id": None, "jsonrpc": "2.0", "result": "result"}, | ||||
|         ]) | ||||
| @ -0,0 +1,150 @@ | ||||
| import json | ||||
| import sys | ||||
| 
 | ||||
| from ..exceptions import ( | ||||
|     JSONRPCError, | ||||
|     JSONRPCInternalError, | ||||
|     JSONRPCInvalidParams, | ||||
|     JSONRPCInvalidRequest, | ||||
|     JSONRPCMethodNotFound, | ||||
|     JSONRPCParseError, | ||||
|     JSONRPCServerError, | ||||
|     JSONRPCDispatchException, | ||||
| ) | ||||
| 
 | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCError(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         self.error_params = { | ||||
|             "code": 0, | ||||
|             "message": "", | ||||
|         } | ||||
| 
 | ||||
|     def test_correct_init(self): | ||||
|         """ Test object is created.""" | ||||
|         JSONRPCError(**self.error_params) | ||||
| 
 | ||||
|     def test_validation_incorrect_no_parameters(self): | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPCError() | ||||
| 
 | ||||
|     def test_code_validation_int(self): | ||||
|         self.error_params.update({"code": 32000}) | ||||
|         JSONRPCError(**self.error_params) | ||||
| 
 | ||||
|     def test_code_validation_no_code(self): | ||||
|         del self.error_params["code"] | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPCError(**self.error_params) | ||||
| 
 | ||||
|     def test_code_validation_str(self): | ||||
|         self.error_params.update({"code": "0"}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPCError(**self.error_params) | ||||
| 
 | ||||
|     def test_message_validation_str(self): | ||||
|         self.error_params.update({"message": ""}) | ||||
|         JSONRPCError(**self.error_params) | ||||
| 
 | ||||
|     def test_message_validation_none(self): | ||||
|         del self.error_params["message"] | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPCError(**self.error_params) | ||||
| 
 | ||||
|     def test_message_validation_int(self): | ||||
|         self.error_params.update({"message": 0}) | ||||
|         with self.assertRaises(ValueError): | ||||
|             JSONRPCError(**self.error_params) | ||||
| 
 | ||||
|     def test_data_validation_none(self): | ||||
|         self.error_params.update({"data": None}) | ||||
|         JSONRPCError(**self.error_params) | ||||
| 
 | ||||
|     def test_data_validation(self): | ||||
|         self.error_params.update({"data": {}}) | ||||
|         JSONRPCError(**self.error_params) | ||||
| 
 | ||||
|         self.error_params.update({"data": ""}) | ||||
|         JSONRPCError(**self.error_params) | ||||
| 
 | ||||
|     def test_json(self): | ||||
|         error = JSONRPCError(**self.error_params) | ||||
|         self.assertEqual( | ||||
|             json.loads(error.json), | ||||
|             self.error_params, | ||||
|         ) | ||||
| 
 | ||||
|     def test_from_json(self): | ||||
|         str_json = json.dumps({ | ||||
|             "code": 0, | ||||
|             "message": "", | ||||
|             "data": {}, | ||||
|         }) | ||||
| 
 | ||||
|         request = JSONRPCError.from_json(str_json) | ||||
|         self.assertTrue(isinstance(request, JSONRPCError)) | ||||
|         self.assertEqual(request.code, 0) | ||||
|         self.assertEqual(request.message, "") | ||||
|         self.assertEqual(request.data, {}) | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCParseError(unittest.TestCase): | ||||
|     def test_code_message(self): | ||||
|         error = JSONRPCParseError() | ||||
|         self.assertEqual(error.code, -32700) | ||||
|         self.assertEqual(error.message, "Parse error") | ||||
|         self.assertEqual(error.data, None) | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCServerError(unittest.TestCase): | ||||
|     def test_code_message(self): | ||||
|         error = JSONRPCServerError() | ||||
|         self.assertEqual(error.code, -32000) | ||||
|         self.assertEqual(error.message, "Server error") | ||||
|         self.assertEqual(error.data, None) | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCInternalError(unittest.TestCase): | ||||
|     def test_code_message(self): | ||||
|         error = JSONRPCInternalError() | ||||
|         self.assertEqual(error.code, -32603) | ||||
|         self.assertEqual(error.message, "Internal error") | ||||
|         self.assertEqual(error.data, None) | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCInvalidParams(unittest.TestCase): | ||||
|     def test_code_message(self): | ||||
|         error = JSONRPCInvalidParams() | ||||
|         self.assertEqual(error.code, -32602) | ||||
|         self.assertEqual(error.message, "Invalid params") | ||||
|         self.assertEqual(error.data, None) | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCInvalidRequest(unittest.TestCase): | ||||
|     def test_code_message(self): | ||||
|         error = JSONRPCInvalidRequest() | ||||
|         self.assertEqual(error.code, -32600) | ||||
|         self.assertEqual(error.message, "Invalid Request") | ||||
|         self.assertEqual(error.data, None) | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCMethodNotFound(unittest.TestCase): | ||||
|     def test_code_message(self): | ||||
|         error = JSONRPCMethodNotFound() | ||||
|         self.assertEqual(error.code, -32601) | ||||
|         self.assertEqual(error.message, "Method not found") | ||||
|         self.assertEqual(error.data, None) | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCDispatchException(unittest.TestCase): | ||||
|     def test_code_message(self): | ||||
|         error = JSONRPCDispatchException(message="message", | ||||
|                                          code=400, data={"param": 1}) | ||||
|         self.assertEqual(error.error.code, 400) | ||||
|         self.assertEqual(error.error.message, "message") | ||||
|         self.assertEqual(error.error.data, {"param": 1}) | ||||
| @ -0,0 +1,175 @@ | ||||
| import sys | ||||
| 
 | ||||
| from ..manager import JSONRPCResponseManager | ||||
| from ..jsonrpc2 import ( | ||||
|     JSONRPC20BatchRequest, | ||||
|     JSONRPC20BatchResponse, | ||||
|     JSONRPC20Request, | ||||
|     JSONRPC20Response, | ||||
| ) | ||||
| from ..jsonrpc1 import JSONRPC10Request, JSONRPC10Response | ||||
| from ..exceptions import JSONRPCDispatchException | ||||
| 
 | ||||
| if sys.version_info < (3, 3): | ||||
|     from mock import MagicMock | ||||
| else: | ||||
|     from unittest.mock import MagicMock | ||||
| 
 | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCResponseManager(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         def raise_(e): | ||||
|             raise e | ||||
| 
 | ||||
|         self.long_time_method = MagicMock() | ||||
|         self.dispatcher = { | ||||
|             "add": sum, | ||||
|             "multiply": lambda a, b: a * b, | ||||
|             "list_len": len, | ||||
|             "101_base": lambda **kwargs: int("101", **kwargs), | ||||
|             "error": lambda: raise_(KeyError("error_explanation")), | ||||
|             "type_error": lambda: raise_(TypeError("TypeError inside method")), | ||||
|             "long_time_method": self.long_time_method, | ||||
|             "dispatch_error": lambda x: raise_( | ||||
|                 JSONRPCDispatchException(code=4000, message="error", | ||||
|                                          data={"param": 1})), | ||||
|         } | ||||
| 
 | ||||
|     def test_dispatch_error(self): | ||||
|         request = JSONRPC20Request("dispatch_error", ["test"], _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "error") | ||||
|         self.assertEqual(response.error["code"], 4000) | ||||
|         self.assertEqual(response.error["data"], {"param": 1}) | ||||
| 
 | ||||
|     def test_returned_type_response(self): | ||||
|         request = JSONRPC20Request("add", [[]], _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
| 
 | ||||
|     def test_returned_type_butch_response(self): | ||||
|         request = JSONRPC20BatchRequest( | ||||
|             JSONRPC20Request("add", [[]], _id=0)) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20BatchResponse)) | ||||
| 
 | ||||
|     def test_returned_type_response_rpc10(self): | ||||
|         request = JSONRPC10Request("add", [[]], _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC10Response)) | ||||
| 
 | ||||
|     def test_parse_error(self): | ||||
|         req = '{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Parse error") | ||||
|         self.assertEqual(response.error["code"], -32700) | ||||
| 
 | ||||
|     def test_invalid_request(self): | ||||
|         req = '{"jsonrpc": "2.0", "method": 1, "params": "bar"}' | ||||
|         response = JSONRPCResponseManager.handle(req, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Invalid Request") | ||||
|         self.assertEqual(response.error["code"], -32600) | ||||
| 
 | ||||
|     def test_method_not_found(self): | ||||
|         request = JSONRPC20Request("does_not_exist", [[]], _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Method not found") | ||||
|         self.assertEqual(response.error["code"], -32601) | ||||
| 
 | ||||
|     def test_invalid_params(self): | ||||
|         request = JSONRPC20Request("add", {"a": 0}, _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Invalid params") | ||||
|         self.assertEqual(response.error["code"], -32602) | ||||
|         self.assertIn(response.error["data"]["message"], [ | ||||
|             'sum() takes no keyword arguments', | ||||
|             "sum() got an unexpected keyword argument 'a'", | ||||
|         ]) | ||||
| 
 | ||||
|     def test_invalid_params_custom_function(self): | ||||
|         request = JSONRPC20Request("multiply", [0], _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Invalid params") | ||||
|         self.assertEqual(response.error["code"], -32602) | ||||
| 
 | ||||
|         request = JSONRPC20Request("multiply", [0, 1, 2], _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Invalid params") | ||||
|         self.assertEqual(response.error["code"], -32602) | ||||
| 
 | ||||
|         request = JSONRPC20Request("multiply", {"a": 1}, _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Invalid params") | ||||
|         self.assertEqual(response.error["code"], -32602) | ||||
| 
 | ||||
|         request = JSONRPC20Request("multiply", {"a": 1, "b": 2, "c": 3}, _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Invalid params") | ||||
|         self.assertEqual(response.error["code"], -32602) | ||||
| 
 | ||||
|     def test_server_error(self): | ||||
|         request = JSONRPC20Request("error", _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Server error") | ||||
|         self.assertEqual(response.error["code"], -32000) | ||||
|         self.assertEqual(response.error["data"]['type'], "KeyError") | ||||
|         self.assertEqual( | ||||
|             response.error["data"]['args'], ('error_explanation',)) | ||||
|         self.assertEqual( | ||||
|             response.error["data"]['message'], "'error_explanation'") | ||||
| 
 | ||||
|     def test_notification_calls_method(self): | ||||
|         request = JSONRPC20Request("long_time_method", is_notification=True) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertEqual(response, None) | ||||
|         self.long_time_method.assert_called_once_with() | ||||
| 
 | ||||
|     def test_notification_does_not_return_error_does_not_exist(self): | ||||
|         request = JSONRPC20Request("does_not_exist", is_notification=True) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertEqual(response, None) | ||||
| 
 | ||||
|     def test_notification_does_not_return_error_invalid_params(self): | ||||
|         request = JSONRPC20Request("add", {"a": 0}, is_notification=True) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertEqual(response, None) | ||||
| 
 | ||||
|     def test_notification_does_not_return_error(self): | ||||
|         request = JSONRPC20Request("error", is_notification=True) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertEqual(response, None) | ||||
| 
 | ||||
|     def test_type_error_inside_method(self): | ||||
|         request = JSONRPC20Request("type_error", _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Server error") | ||||
|         self.assertEqual(response.error["code"], -32000) | ||||
|         self.assertEqual(response.error["data"]['type'], "TypeError") | ||||
|         self.assertEqual( | ||||
|             response.error["data"]['args'], ('TypeError inside method',)) | ||||
|         self.assertEqual( | ||||
|             response.error["data"]['message'], 'TypeError inside method') | ||||
| 
 | ||||
|     def test_invalid_params_before_dispatcher_error(self): | ||||
|         request = JSONRPC20Request( | ||||
|             "dispatch_error", ["invalid", "params"], _id=0) | ||||
|         response = JSONRPCResponseManager.handle(request.json, self.dispatcher) | ||||
|         self.assertTrue(isinstance(response, JSONRPC20Response)) | ||||
|         self.assertEqual(response.error["message"], "Invalid params") | ||||
|         self.assertEqual(response.error["code"], -32602) | ||||
| @ -0,0 +1,28 @@ | ||||
| from ..manager import JSONRPCResponseManager | ||||
| 
 | ||||
| import sys | ||||
| 
 | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONRPCResponseManager(unittest.TestCase): | ||||
|     @unittest.skipIf(sys.version_info < (3, 5), "Test Py3.5+ functionality") | ||||
|     def test_typeerror_with_annotations(self): | ||||
|         """If a function has Python3 annotations and is called with improper | ||||
|         arguments, make sure the framework doesn't fail with inspect.getargspec | ||||
|         """ | ||||
|         from .py35_utils import distance | ||||
| 
 | ||||
|         dispatcher = { | ||||
|             "distance": distance, | ||||
|         } | ||||
| 
 | ||||
|         req = '{"jsonrpc": "2.0", "method": "distance", "params": [], "id": 1}' | ||||
|         result = JSONRPCResponseManager.handle(req, dispatcher) | ||||
| 
 | ||||
|         # Make sure this returns JSONRPCInvalidParams rather than raising | ||||
|         # UnboundLocalError | ||||
|         self.assertEqual(result.error['code'], -32602) | ||||
| @ -0,0 +1,130 @@ | ||||
| """ Test utility functionality.""" | ||||
| from ..utils import JSONSerializable, DatetimeDecimalEncoder, is_invalid_params | ||||
| 
 | ||||
| import datetime | ||||
| import decimal | ||||
| import json | ||||
| import sys | ||||
| 
 | ||||
| if sys.version_info < (3, 3): | ||||
|     from mock import patch | ||||
| else: | ||||
|     from unittest.mock import patch | ||||
| 
 | ||||
| if sys.version_info < (2, 7): | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| 
 | ||||
| class TestJSONSerializable(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test JSONSerializable functionality.""" | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         class A(JSONSerializable): | ||||
|             @property | ||||
|             def json(self): | ||||
|                 pass | ||||
| 
 | ||||
|         self._class = A | ||||
| 
 | ||||
|     def test_abstract_class(self): | ||||
|         with self.assertRaises(TypeError): | ||||
|             JSONSerializable() | ||||
| 
 | ||||
|         self._class() | ||||
| 
 | ||||
|     def test_definse_serialize_deserialize(self): | ||||
|         """ Test classmethods of inherited class.""" | ||||
|         self.assertEqual(self._class.serialize({}), "{}") | ||||
|         self.assertEqual(self._class.deserialize("{}"), {}) | ||||
| 
 | ||||
|     def test_from_json(self): | ||||
|         self.assertTrue(isinstance(self._class.from_json('{}'), self._class)) | ||||
| 
 | ||||
|     def test_from_json_incorrect(self): | ||||
|         with self.assertRaises(ValueError): | ||||
|             self._class.from_json('[]') | ||||
| 
 | ||||
| 
 | ||||
| class TestDatetimeDecimalEncoder(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test DatetimeDecimalEncoder functionality.""" | ||||
| 
 | ||||
|     def test_date_encoder(self): | ||||
|         obj = datetime.date.today() | ||||
| 
 | ||||
|         with self.assertRaises(TypeError): | ||||
|             json.dumps(obj) | ||||
| 
 | ||||
|         self.assertEqual( | ||||
|             json.dumps(obj, cls=DatetimeDecimalEncoder), | ||||
|             '"{0}"'.format(obj.isoformat()), | ||||
|         ) | ||||
| 
 | ||||
|     def test_datetime_encoder(self): | ||||
|         obj = datetime.datetime.now() | ||||
| 
 | ||||
|         with self.assertRaises(TypeError): | ||||
|             json.dumps(obj) | ||||
| 
 | ||||
|         self.assertEqual( | ||||
|             json.dumps(obj, cls=DatetimeDecimalEncoder), | ||||
|             '"{0}"'.format(obj.isoformat()), | ||||
|         ) | ||||
| 
 | ||||
|     def test_decimal_encoder(self): | ||||
|         obj = decimal.Decimal('0.1') | ||||
| 
 | ||||
|         with self.assertRaises(TypeError): | ||||
|             json.dumps(obj) | ||||
| 
 | ||||
|         result = json.dumps(obj, cls=DatetimeDecimalEncoder) | ||||
|         self.assertTrue(isinstance(result, str)) | ||||
|         self.assertEqual(float(result), float(0.1)) | ||||
| 
 | ||||
|     def test_default(self): | ||||
|         encoder = DatetimeDecimalEncoder() | ||||
|         with patch.object(json.JSONEncoder, 'default') as json_default: | ||||
|             encoder.default("") | ||||
| 
 | ||||
|         self.assertEqual(json_default.call_count, 1) | ||||
| 
 | ||||
| 
 | ||||
| class TestUtils(unittest.TestCase): | ||||
| 
 | ||||
|     """ Test utils functions.""" | ||||
| 
 | ||||
|     def test_is_invalid_params_builtin(self): | ||||
|         self.assertTrue(is_invalid_params(sum, 0, 0)) | ||||
|         # NOTE: builtin functions could not be recognized by inspect.isfunction | ||||
|         # It would raise TypeError if parameters are incorrect already. | ||||
|         # self.assertFalse(is_invalid_params(sum, [0, 0]))  # <- fails | ||||
| 
 | ||||
|     def test_is_invalid_params_args(self): | ||||
|         self.assertTrue(is_invalid_params(lambda a, b: None, 0)) | ||||
|         self.assertTrue(is_invalid_params(lambda a, b: None, 0, 1, 2)) | ||||
| 
 | ||||
|     def test_is_invalid_params_kwargs(self): | ||||
|         self.assertTrue(is_invalid_params(lambda a: None, **{})) | ||||
|         self.assertTrue(is_invalid_params(lambda a: None, **{"a": 0, "b": 1})) | ||||
| 
 | ||||
|     def test_invalid_params_correct(self): | ||||
|         self.assertFalse(is_invalid_params(lambda: None)) | ||||
|         self.assertFalse(is_invalid_params(lambda a: None, 0)) | ||||
|         self.assertFalse(is_invalid_params(lambda a, b=0: None, 0)) | ||||
|         self.assertFalse(is_invalid_params(lambda a, b=0: None, 0, 0)) | ||||
| 
 | ||||
|     def test_is_invalid_params_mixed(self): | ||||
|         self.assertFalse(is_invalid_params(lambda a, b: None, 0, **{"b": 1})) | ||||
|         self.assertFalse(is_invalid_params( | ||||
|             lambda a, b, c=0: None, 0, **{"b": 1})) | ||||
| 
 | ||||
|     def test_is_invalid_params_py2(self): | ||||
|         with patch('jsonrpc.utils.sys') as mock_sys: | ||||
|             mock_sys.version_info = (2, 7) | ||||
|             with patch('jsonrpc.utils.is_invalid_params_py2') as mock_func: | ||||
|                 is_invalid_params(lambda a: None, 0) | ||||
| 
 | ||||
|         assert mock_func.call_count == 1 | ||||
| @ -0,0 +1,135 @@ | ||||
| """ Utility functions for package.""" | ||||
| from abc import ABCMeta, abstractmethod | ||||
| import datetime | ||||
| import decimal | ||||
| import inspect | ||||
| import json | ||||
| import sys | ||||
| 
 | ||||
| from . import six | ||||
| 
 | ||||
| 
 | ||||
| class JSONSerializable(six.with_metaclass(ABCMeta, object)): | ||||
| 
 | ||||
|     """ Common functionality for json serializable objects.""" | ||||
| 
 | ||||
|     serialize = staticmethod(json.dumps) | ||||
|     deserialize = staticmethod(json.loads) | ||||
| 
 | ||||
|     @abstractmethod | ||||
|     def json(self): | ||||
|         raise NotImplementedError() | ||||
| 
 | ||||
|     @classmethod | ||||
|     def from_json(cls, json_str): | ||||
|         data = cls.deserialize(json_str) | ||||
| 
 | ||||
|         if not isinstance(data, dict): | ||||
|             raise ValueError("data should be dict") | ||||
| 
 | ||||
|         return cls(**data) | ||||
| 
 | ||||
| 
 | ||||
| class DatetimeDecimalEncoder(json.JSONEncoder): | ||||
| 
 | ||||
|     """ Encoder for datetime and decimal serialization. | ||||
| 
 | ||||
|     Usage: json.dumps(object, cls=DatetimeDecimalEncoder) | ||||
|     NOTE: _iterencode does not work | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     def default(self, o): | ||||
|         """ Encode JSON. | ||||
| 
 | ||||
|         :return str: A JSON encoded string | ||||
| 
 | ||||
|         """ | ||||
|         if isinstance(o, decimal.Decimal): | ||||
|             return float(o) | ||||
| 
 | ||||
|         if isinstance(o, (datetime.datetime, datetime.date)): | ||||
|             return o.isoformat() | ||||
| 
 | ||||
|         return json.JSONEncoder.default(self, o) | ||||
| 
 | ||||
| 
 | ||||
| def is_invalid_params_py2(func, *args, **kwargs): | ||||
|     """ Check, whether function 'func' accepts parameters 'args', 'kwargs'. | ||||
| 
 | ||||
|     NOTE: Method is called after funct(*args, **kwargs) generated TypeError, | ||||
|     it is aimed to destinguish TypeError because of invalid parameters from | ||||
|     TypeError from inside the function. | ||||
| 
 | ||||
|     .. versionadded: 1.9.0 | ||||
| 
 | ||||
|     """ | ||||
|     funcargs, varargs, varkwargs, defaults = inspect.getargspec(func) | ||||
| 
 | ||||
|     unexpected = set(kwargs.keys()) - set(funcargs) | ||||
|     if len(unexpected) > 0: | ||||
|         return True | ||||
| 
 | ||||
|     params = [funcarg for funcarg in funcargs if funcarg not in kwargs] | ||||
|     funcargs_required = funcargs[:-len(defaults)] \ | ||||
|         if defaults is not None \ | ||||
|         else funcargs | ||||
|     params_required = [ | ||||
|         funcarg for funcarg in funcargs_required | ||||
|         if funcarg not in kwargs | ||||
|     ] | ||||
| 
 | ||||
|     return not (len(params_required) <= len(args) <= len(params)) | ||||
| 
 | ||||
| 
 | ||||
| def is_invalid_params_py3(func, *args, **kwargs): | ||||
|     """ | ||||
|     Use inspect.signature instead of inspect.getargspec or | ||||
|     inspect.getfullargspec (based on inspect.signature itself) as it provides | ||||
|     more information about function parameters. | ||||
| 
 | ||||
|     .. versionadded: 1.11.2 | ||||
| 
 | ||||
|     """ | ||||
|     signature = inspect.signature(func) | ||||
|     parameters = signature.parameters | ||||
| 
 | ||||
|     unexpected = set(kwargs.keys()) - set(parameters.keys()) | ||||
|     if len(unexpected) > 0: | ||||
|         return True | ||||
| 
 | ||||
|     params = [ | ||||
|         parameter for name, parameter in parameters.items() | ||||
|         if name not in kwargs | ||||
|     ] | ||||
|     params_required = [ | ||||
|         param for param in params | ||||
|         if param.default is param.empty | ||||
|     ] | ||||
| 
 | ||||
|     return not (len(params_required) <= len(args) <= len(params)) | ||||
| 
 | ||||
| 
 | ||||
| def is_invalid_params(func, *args, **kwargs): | ||||
|     """ | ||||
|     Method: | ||||
|         Validate pre-defined criteria, if any is True - function is invalid | ||||
|         0. func should be callable | ||||
|         1. kwargs should not have unexpected keywords | ||||
|         2. remove kwargs.keys from func.parameters | ||||
|         3. number of args should be <= remaining func.parameters | ||||
|         4. number of args should be >= remaining func.parameters less default | ||||
|     """ | ||||
|     # For builtin functions inspect.getargspec(funct) return error. If builtin | ||||
|     # function generates TypeError, it is because of wrong parameters. | ||||
|     if not inspect.isfunction(func): | ||||
|         return True | ||||
| 
 | ||||
|     if sys.version_info >= (3, 3): | ||||
|         return is_invalid_params_py3(func, *args, **kwargs) | ||||
|     else: | ||||
|         # NOTE: use Python2 method for Python 3.2 as well. Starting from Python | ||||
|         # 3.3 it is recommended to use inspect.signature instead. | ||||
|         # In Python 3.0 - 3.2 inspect.getfullargspec is preferred but these | ||||
|         # versions are almost not supported. Users should consider upgrading. | ||||
|         return is_invalid_params_py2(func, *args, **kwargs) | ||||
| @ -0,0 +1,29 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA  02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| from ._abnf import * | ||||
| from ._app import WebSocketApp | ||||
| from ._core import * | ||||
| from ._exceptions import * | ||||
| from ._logging import * | ||||
| from ._socket import * | ||||
| 
 | ||||
| __version__ = "0.55.0" | ||||
| @ -0,0 +1,447 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA  02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| import array | ||||
| import os | ||||
| import struct | ||||
| 
 | ||||
| import six | ||||
| 
 | ||||
| from ._exceptions import * | ||||
| from ._utils import validate_utf8 | ||||
| from threading import Lock | ||||
| 
 | ||||
| try: | ||||
|     if six.PY3: | ||||
|         import numpy | ||||
|     else: | ||||
|         numpy = None | ||||
| except ImportError: | ||||
|     numpy = None | ||||
| 
 | ||||
| try: | ||||
|     # If wsaccel is available we use compiled routines to mask data. | ||||
|     if not numpy: | ||||
|         from wsaccel.xormask import XorMaskerSimple | ||||
| 
 | ||||
|         def _mask(_m, _d): | ||||
|             return XorMaskerSimple(_m).process(_d) | ||||
| except ImportError: | ||||
|     # wsaccel is not available, we rely on python implementations. | ||||
|     def _mask(_m, _d): | ||||
|         for i in range(len(_d)): | ||||
|             _d[i] ^= _m[i % 4] | ||||
| 
 | ||||
|         if six.PY3: | ||||
|             return _d.tobytes() | ||||
|         else: | ||||
|             return _d.tostring() | ||||
| 
 | ||||
| 
 | ||||
| __all__ = [ | ||||
|     'ABNF', 'continuous_frame', 'frame_buffer', | ||||
|     'STATUS_NORMAL', | ||||
|     'STATUS_GOING_AWAY', | ||||
|     'STATUS_PROTOCOL_ERROR', | ||||
|     'STATUS_UNSUPPORTED_DATA_TYPE', | ||||
|     'STATUS_STATUS_NOT_AVAILABLE', | ||||
|     'STATUS_ABNORMAL_CLOSED', | ||||
|     'STATUS_INVALID_PAYLOAD', | ||||
|     'STATUS_POLICY_VIOLATION', | ||||
|     'STATUS_MESSAGE_TOO_BIG', | ||||
|     'STATUS_INVALID_EXTENSION', | ||||
|     'STATUS_UNEXPECTED_CONDITION', | ||||
|     'STATUS_BAD_GATEWAY', | ||||
|     'STATUS_TLS_HANDSHAKE_ERROR', | ||||
| ] | ||||
| 
 | ||||
| # closing frame status codes. | ||||
| STATUS_NORMAL = 1000 | ||||
| STATUS_GOING_AWAY = 1001 | ||||
| STATUS_PROTOCOL_ERROR = 1002 | ||||
| STATUS_UNSUPPORTED_DATA_TYPE = 1003 | ||||
| STATUS_STATUS_NOT_AVAILABLE = 1005 | ||||
| STATUS_ABNORMAL_CLOSED = 1006 | ||||
| STATUS_INVALID_PAYLOAD = 1007 | ||||
| STATUS_POLICY_VIOLATION = 1008 | ||||
| STATUS_MESSAGE_TOO_BIG = 1009 | ||||
| STATUS_INVALID_EXTENSION = 1010 | ||||
| STATUS_UNEXPECTED_CONDITION = 1011 | ||||
| STATUS_BAD_GATEWAY = 1014 | ||||
| STATUS_TLS_HANDSHAKE_ERROR = 1015 | ||||
| 
 | ||||
| VALID_CLOSE_STATUS = ( | ||||
|     STATUS_NORMAL, | ||||
|     STATUS_GOING_AWAY, | ||||
|     STATUS_PROTOCOL_ERROR, | ||||
|     STATUS_UNSUPPORTED_DATA_TYPE, | ||||
|     STATUS_INVALID_PAYLOAD, | ||||
|     STATUS_POLICY_VIOLATION, | ||||
|     STATUS_MESSAGE_TOO_BIG, | ||||
|     STATUS_INVALID_EXTENSION, | ||||
|     STATUS_UNEXPECTED_CONDITION, | ||||
|     STATUS_BAD_GATEWAY, | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| class ABNF(object): | ||||
|     """ | ||||
|     ABNF frame class. | ||||
|     see http://tools.ietf.org/html/rfc5234 | ||||
|     and http://tools.ietf.org/html/rfc6455#section-5.2 | ||||
|     """ | ||||
| 
 | ||||
|     # operation code values. | ||||
|     OPCODE_CONT = 0x0 | ||||
|     OPCODE_TEXT = 0x1 | ||||
|     OPCODE_BINARY = 0x2 | ||||
|     OPCODE_CLOSE = 0x8 | ||||
|     OPCODE_PING = 0x9 | ||||
|     OPCODE_PONG = 0xa | ||||
| 
 | ||||
|     # available operation code value tuple | ||||
|     OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, | ||||
|                OPCODE_PING, OPCODE_PONG) | ||||
| 
 | ||||
|     # opcode human readable string | ||||
|     OPCODE_MAP = { | ||||
|         OPCODE_CONT: "cont", | ||||
|         OPCODE_TEXT: "text", | ||||
|         OPCODE_BINARY: "binary", | ||||
|         OPCODE_CLOSE: "close", | ||||
|         OPCODE_PING: "ping", | ||||
|         OPCODE_PONG: "pong" | ||||
|     } | ||||
| 
 | ||||
|     # data length threshold. | ||||
|     LENGTH_7 = 0x7e | ||||
|     LENGTH_16 = 1 << 16 | ||||
|     LENGTH_63 = 1 << 63 | ||||
| 
 | ||||
|     def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0, | ||||
|                  opcode=OPCODE_TEXT, mask=1, data=""): | ||||
|         """ | ||||
|         Constructor for ABNF. | ||||
|         please check RFC for arguments. | ||||
|         """ | ||||
|         self.fin = fin | ||||
|         self.rsv1 = rsv1 | ||||
|         self.rsv2 = rsv2 | ||||
|         self.rsv3 = rsv3 | ||||
|         self.opcode = opcode | ||||
|         self.mask = mask | ||||
|         if data is None: | ||||
|             data = "" | ||||
|         self.data = data | ||||
|         self.get_mask_key = os.urandom | ||||
| 
 | ||||
|     def validate(self, skip_utf8_validation=False): | ||||
|         """ | ||||
|         validate the ABNF frame. | ||||
|         skip_utf8_validation: skip utf8 validation. | ||||
|         """ | ||||
|         if self.rsv1 or self.rsv2 or self.rsv3: | ||||
|             raise WebSocketProtocolException("rsv is not implemented, yet") | ||||
| 
 | ||||
|         if self.opcode not in ABNF.OPCODES: | ||||
|             raise WebSocketProtocolException("Invalid opcode %r", self.opcode) | ||||
| 
 | ||||
|         if self.opcode == ABNF.OPCODE_PING and not self.fin: | ||||
|             raise WebSocketProtocolException("Invalid ping frame.") | ||||
| 
 | ||||
|         if self.opcode == ABNF.OPCODE_CLOSE: | ||||
|             l = len(self.data) | ||||
|             if not l: | ||||
|                 return | ||||
|             if l == 1 or l >= 126: | ||||
|                 raise WebSocketProtocolException("Invalid close frame.") | ||||
|             if l > 2 and not skip_utf8_validation and not validate_utf8(self.data[2:]): | ||||
|                 raise WebSocketProtocolException("Invalid close frame.") | ||||
| 
 | ||||
|             code = 256 * \ | ||||
|                 six.byte2int(self.data[0:1]) + six.byte2int(self.data[1:2]) | ||||
|             if not self._is_valid_close_status(code): | ||||
|                 raise WebSocketProtocolException("Invalid close opcode.") | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _is_valid_close_status(code): | ||||
|         return code in VALID_CLOSE_STATUS or (3000 <= code < 5000) | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         return "fin=" + str(self.fin) \ | ||||
|             + " opcode=" + str(self.opcode) \ | ||||
|             + " data=" + str(self.data) | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def create_frame(data, opcode, fin=1): | ||||
|         """ | ||||
|         create frame to send text, binary and other data. | ||||
| 
 | ||||
|         data: data to send. This is string value(byte array). | ||||
|             if opcode is OPCODE_TEXT and this value is unicode, | ||||
|             data value is converted into unicode string, automatically. | ||||
| 
 | ||||
|         opcode: operation code. please see OPCODE_XXX. | ||||
| 
 | ||||
|         fin: fin flag. if set to 0, create continue fragmentation. | ||||
|         """ | ||||
|         if opcode == ABNF.OPCODE_TEXT and isinstance(data, six.text_type): | ||||
|             data = data.encode("utf-8") | ||||
|         # mask must be set if send data from client | ||||
|         return ABNF(fin, 0, 0, 0, opcode, 1, data) | ||||
| 
 | ||||
|     def format(self): | ||||
|         """ | ||||
|         format this object to string(byte array) to send data to server. | ||||
|         """ | ||||
|         if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]): | ||||
|             raise ValueError("not 0 or 1") | ||||
|         if self.opcode not in ABNF.OPCODES: | ||||
|             raise ValueError("Invalid OPCODE") | ||||
|         length = len(self.data) | ||||
|         if length >= ABNF.LENGTH_63: | ||||
|             raise ValueError("data is too long") | ||||
| 
 | ||||
|         frame_header = chr(self.fin << 7 | ||||
|                            | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 | ||||
|                            | self.opcode) | ||||
|         if length < ABNF.LENGTH_7: | ||||
|             frame_header += chr(self.mask << 7 | length) | ||||
|             frame_header = six.b(frame_header) | ||||
|         elif length < ABNF.LENGTH_16: | ||||
|             frame_header += chr(self.mask << 7 | 0x7e) | ||||
|             frame_header = six.b(frame_header) | ||||
|             frame_header += struct.pack("!H", length) | ||||
|         else: | ||||
|             frame_header += chr(self.mask << 7 | 0x7f) | ||||
|             frame_header = six.b(frame_header) | ||||
|             frame_header += struct.pack("!Q", length) | ||||
| 
 | ||||
|         if not self.mask: | ||||
|             return frame_header + self.data | ||||
|         else: | ||||
|             mask_key = self.get_mask_key(4) | ||||
|             return frame_header + self._get_masked(mask_key) | ||||
| 
 | ||||
|     def _get_masked(self, mask_key): | ||||
|         s = ABNF.mask(mask_key, self.data) | ||||
| 
 | ||||
|         if isinstance(mask_key, six.text_type): | ||||
|             mask_key = mask_key.encode('utf-8') | ||||
| 
 | ||||
|         return mask_key + s | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def mask(mask_key, data): | ||||
|         """ | ||||
|         mask or unmask data. Just do xor for each byte | ||||
| 
 | ||||
|         mask_key: 4 byte string(byte). | ||||
| 
 | ||||
|         data: data to mask/unmask. | ||||
|         """ | ||||
|         if data is None: | ||||
|             data = "" | ||||
| 
 | ||||
|         if isinstance(mask_key, six.text_type): | ||||
|             mask_key = six.b(mask_key) | ||||
| 
 | ||||
|         if isinstance(data, six.text_type): | ||||
|             data = six.b(data) | ||||
| 
 | ||||
|         if numpy: | ||||
|             origlen = len(data) | ||||
|             _mask_key = mask_key[3] << 24 | mask_key[2] << 16 | mask_key[1] << 8 | mask_key[0] | ||||
| 
 | ||||
|             # We need data to be a multiple of four... | ||||
|             data += bytes(" " * (4 - (len(data) % 4)), "us-ascii") | ||||
|             a = numpy.frombuffer(data, dtype="uint32") | ||||
|             masked = numpy.bitwise_xor(a, [_mask_key]).astype("uint32") | ||||
|             if len(data) > origlen: | ||||
|               return masked.tobytes()[:origlen] | ||||
|             return masked.tobytes() | ||||
|         else: | ||||
|             _m = array.array("B", mask_key) | ||||
|             _d = array.array("B", data) | ||||
|             return _mask(_m, _d) | ||||
| 
 | ||||
| 
 | ||||
| class frame_buffer(object): | ||||
|     _HEADER_MASK_INDEX = 5 | ||||
|     _HEADER_LENGTH_INDEX = 6 | ||||
| 
 | ||||
|     def __init__(self, recv_fn, skip_utf8_validation): | ||||
|         self.recv = recv_fn | ||||
|         self.skip_utf8_validation = skip_utf8_validation | ||||
|         # Buffers over the packets from the layer beneath until desired amount | ||||
|         # bytes of bytes are received. | ||||
|         self.recv_buffer = [] | ||||
|         self.clear() | ||||
|         self.lock = Lock() | ||||
| 
 | ||||
|     def clear(self): | ||||
|         self.header = None | ||||
|         self.length = None | ||||
|         self.mask = None | ||||
| 
 | ||||
|     def has_received_header(self): | ||||
|         return self.header is None | ||||
| 
 | ||||
|     def recv_header(self): | ||||
|         header = self.recv_strict(2) | ||||
|         b1 = header[0] | ||||
| 
 | ||||
|         if six.PY2: | ||||
|             b1 = ord(b1) | ||||
| 
 | ||||
|         fin = b1 >> 7 & 1 | ||||
|         rsv1 = b1 >> 6 & 1 | ||||
|         rsv2 = b1 >> 5 & 1 | ||||
|         rsv3 = b1 >> 4 & 1 | ||||
|         opcode = b1 & 0xf | ||||
|         b2 = header[1] | ||||
| 
 | ||||
|         if six.PY2: | ||||
|             b2 = ord(b2) | ||||
| 
 | ||||
|         has_mask = b2 >> 7 & 1 | ||||
|         length_bits = b2 & 0x7f | ||||
| 
 | ||||
|         self.header = (fin, rsv1, rsv2, rsv3, opcode, has_mask, length_bits) | ||||
| 
 | ||||
|     def has_mask(self): | ||||
|         if not self.header: | ||||
|             return False | ||||
|         return self.header[frame_buffer._HEADER_MASK_INDEX] | ||||
| 
 | ||||
|     def has_received_length(self): | ||||
|         return self.length is None | ||||
| 
 | ||||
|     def recv_length(self): | ||||
|         bits = self.header[frame_buffer._HEADER_LENGTH_INDEX] | ||||
|         length_bits = bits & 0x7f | ||||
|         if length_bits == 0x7e: | ||||
|             v = self.recv_strict(2) | ||||
|             self.length = struct.unpack("!H", v)[0] | ||||
|         elif length_bits == 0x7f: | ||||
|             v = self.recv_strict(8) | ||||
|             self.length = struct.unpack("!Q", v)[0] | ||||
|         else: | ||||
|             self.length = length_bits | ||||
| 
 | ||||
|     def has_received_mask(self): | ||||
|         return self.mask is None | ||||
| 
 | ||||
|     def recv_mask(self): | ||||
|         self.mask = self.recv_strict(4) if self.has_mask() else "" | ||||
| 
 | ||||
|     def recv_frame(self): | ||||
| 
 | ||||
|         with self.lock: | ||||
|             # Header | ||||
|             if self.has_received_header(): | ||||
|                 self.recv_header() | ||||
|             (fin, rsv1, rsv2, rsv3, opcode, has_mask, _) = self.header | ||||
| 
 | ||||
|             # Frame length | ||||
|             if self.has_received_length(): | ||||
|                 self.recv_length() | ||||
|             length = self.length | ||||
| 
 | ||||
|             # Mask | ||||
|             if self.has_received_mask(): | ||||
|                 self.recv_mask() | ||||
|             mask = self.mask | ||||
| 
 | ||||
|             # Payload | ||||
|             payload = self.recv_strict(length) | ||||
|             if has_mask: | ||||
|                 payload = ABNF.mask(mask, payload) | ||||
| 
 | ||||
|             # Reset for next frame | ||||
|             self.clear() | ||||
| 
 | ||||
|             frame = ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload) | ||||
|             frame.validate(self.skip_utf8_validation) | ||||
| 
 | ||||
|         return frame | ||||
| 
 | ||||
|     def recv_strict(self, bufsize): | ||||
|         shortage = bufsize - sum(len(x) for x in self.recv_buffer) | ||||
|         while shortage > 0: | ||||
|             # Limit buffer size that we pass to socket.recv() to avoid | ||||
|             # fragmenting the heap -- the number of bytes recv() actually | ||||
|             # reads is limited by socket buffer and is relatively small, | ||||
|             # yet passing large numbers repeatedly causes lots of large | ||||
|             # buffers allocated and then shrunk, which results in | ||||
|             # fragmentation. | ||||
|             bytes_ = self.recv(min(16384, shortage)) | ||||
|             self.recv_buffer.append(bytes_) | ||||
|             shortage -= len(bytes_) | ||||
| 
 | ||||
|         unified = six.b("").join(self.recv_buffer) | ||||
| 
 | ||||
|         if shortage == 0: | ||||
|             self.recv_buffer = [] | ||||
|             return unified | ||||
|         else: | ||||
|             self.recv_buffer = [unified[bufsize:]] | ||||
|             return unified[:bufsize] | ||||
| 
 | ||||
| 
 | ||||
| class continuous_frame(object): | ||||
| 
 | ||||
|     def __init__(self, fire_cont_frame, skip_utf8_validation): | ||||
|         self.fire_cont_frame = fire_cont_frame | ||||
|         self.skip_utf8_validation = skip_utf8_validation | ||||
|         self.cont_data = None | ||||
|         self.recving_frames = None | ||||
| 
 | ||||
|     def validate(self, frame): | ||||
|         if not self.recving_frames and frame.opcode == ABNF.OPCODE_CONT: | ||||
|             raise WebSocketProtocolException("Illegal frame") | ||||
|         if self.recving_frames and \ | ||||
|                 frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY): | ||||
|             raise WebSocketProtocolException("Illegal frame") | ||||
| 
 | ||||
|     def add(self, frame): | ||||
|         if self.cont_data: | ||||
|             self.cont_data[1] += frame.data | ||||
|         else: | ||||
|             if frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY): | ||||
|                 self.recving_frames = frame.opcode | ||||
|             self.cont_data = [frame.opcode, frame.data] | ||||
| 
 | ||||
|         if frame.fin: | ||||
|             self.recving_frames = None | ||||
| 
 | ||||
|     def is_fire(self, frame): | ||||
|         return frame.fin or self.fire_cont_frame | ||||
| 
 | ||||
|     def extract(self, frame): | ||||
|         data = self.cont_data | ||||
|         self.cont_data = None | ||||
|         frame.data = data[1] | ||||
|         if not self.fire_cont_frame and data[0] == ABNF.OPCODE_TEXT and not self.skip_utf8_validation and not validate_utf8(frame.data): | ||||
|             raise WebSocketPayloadException( | ||||
|                 "cannot decode: " + repr(frame.data)) | ||||
| 
 | ||||
|         return [data[0], frame] | ||||
| @ -0,0 +1,351 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA  02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| """ | ||||
| WebSocketApp provides higher level APIs. | ||||
| """ | ||||
| import inspect | ||||
| import select | ||||
| import sys | ||||
| import threading | ||||
| import time | ||||
| import traceback | ||||
| 
 | ||||
| import six | ||||
| 
 | ||||
| from ._abnf import ABNF | ||||
| from ._core import WebSocket, getdefaulttimeout | ||||
| from ._exceptions import * | ||||
| from . import _logging | ||||
| 
 | ||||
| 
 | ||||
| __all__ = ["WebSocketApp"] | ||||
| 
 | ||||
| class Dispatcher: | ||||
|     def __init__(self, app, ping_timeout): | ||||
|         self.app  = app | ||||
|         self.ping_timeout = ping_timeout | ||||
| 
 | ||||
|     def read(self, sock, read_callback, check_callback): | ||||
|         while self.app.sock.connected: | ||||
|             r, w, e = select.select( | ||||
|                     (self.app.sock.sock, ), (), (), self.ping_timeout) | ||||
|             if r: | ||||
|                 if not read_callback(): | ||||
|                     break | ||||
|             check_callback() | ||||
| 
 | ||||
| class SSLDispacther: | ||||
|     def __init__(self, app, ping_timeout): | ||||
|         self.app  = app | ||||
|         self.ping_timeout = ping_timeout | ||||
| 
 | ||||
|     def read(self, sock, read_callback, check_callback): | ||||
|         while self.app.sock.connected: | ||||
|             r = self.select() | ||||
|             if r: | ||||
|                 if not read_callback(): | ||||
|                     break | ||||
|             check_callback() | ||||
| 
 | ||||
|     def select(self): | ||||
|         sock = self.app.sock.sock | ||||
|         if sock.pending(): | ||||
|             return [sock,] | ||||
| 
 | ||||
|         r, w, e = select.select((sock, ), (), (), self.ping_timeout) | ||||
|         return r | ||||
| 
 | ||||
| class WebSocketApp(object): | ||||
|     """ | ||||
|     Higher level of APIs are provided. | ||||
|     The interface is like JavaScript WebSocket object. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, url, header=None, | ||||
|                  on_open=None, on_message=None, on_error=None, | ||||
|                  on_close=None, on_ping=None, on_pong=None, | ||||
|                  on_cont_message=None, | ||||
|                  keep_running=True, get_mask_key=None, cookie=None, | ||||
|                  subprotocols=None, | ||||
|                  on_data=None): | ||||
|         """ | ||||
|         url: websocket url. | ||||
|         header: custom header for websocket handshake. | ||||
|         on_open: callable object which is called at opening websocket. | ||||
|           this function has one argument. The argument is this class object. | ||||
|         on_message: callable object which is called when received data. | ||||
|          on_message has 2 arguments. | ||||
|          The 1st argument is this class object. | ||||
|          The 2nd argument is utf-8 string which we get from the server. | ||||
|         on_error: callable object which is called when we get error. | ||||
|          on_error has 2 arguments. | ||||
|          The 1st argument is this class object. | ||||
|          The 2nd argument is exception object. | ||||
|         on_close: callable object which is called when closed the connection. | ||||
|          this function has one argument. The argument is this class object. | ||||
|         on_cont_message: callback object which is called when receive continued | ||||
|          frame data. | ||||
|          on_cont_message has 3 arguments. | ||||
|          The 1st argument is this class object. | ||||
|          The 2nd argument is utf-8 string which we get from the server. | ||||
|          The 3rd argument is continue flag. if 0, the data continue | ||||
|          to next frame data | ||||
|         on_data: callback object which is called when a message received. | ||||
|           This is called before on_message or on_cont_message, | ||||
|           and then on_message or on_cont_message is called. | ||||
|           on_data has 4 argument. | ||||
|           The 1st argument is this class object. | ||||
|           The 2nd argument is utf-8 string which we get from the server. | ||||
|           The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came. | ||||
|           The 4th argument is continue flag. if 0, the data continue | ||||
|         keep_running: this parameter is obsolete and ignored. | ||||
|         get_mask_key: a callable to produce new mask keys, | ||||
|           see the WebSocket.set_mask_key's docstring for more information | ||||
|         subprotocols: array of available sub protocols. default is None. | ||||
|         """ | ||||
|         self.url = url | ||||
|         self.header = header if header is not None else [] | ||||
|         self.cookie = cookie | ||||
| 
 | ||||
|         self.on_open = on_open | ||||
|         self.on_message = on_message | ||||
|         self.on_data = on_data | ||||
|         self.on_error = on_error | ||||
|         self.on_close = on_close | ||||
|         self.on_ping = on_ping | ||||
|         self.on_pong = on_pong | ||||
|         self.on_cont_message = on_cont_message | ||||
|         self.keep_running = False | ||||
|         self.get_mask_key = get_mask_key | ||||
|         self.sock = None | ||||
|         self.last_ping_tm = 0 | ||||
|         self.last_pong_tm = 0 | ||||
|         self.subprotocols = subprotocols | ||||
| 
 | ||||
|     def send(self, data, opcode=ABNF.OPCODE_TEXT): | ||||
|         """ | ||||
|         send message. | ||||
|         data: message to send. If you set opcode to OPCODE_TEXT, | ||||
|               data must be utf-8 string or unicode. | ||||
|         opcode: operation code of data. default is OPCODE_TEXT. | ||||
|         """ | ||||
| 
 | ||||
|         if not self.sock or self.sock.send(data, opcode) == 0: | ||||
|             raise WebSocketConnectionClosedException( | ||||
|                 "Connection is already closed.") | ||||
| 
 | ||||
|     def close(self, **kwargs): | ||||
|         """ | ||||
|         close websocket connection. | ||||
|         """ | ||||
|         self.keep_running = False | ||||
|         if self.sock: | ||||
|             self.sock.close(**kwargs) | ||||
|             self.sock = None | ||||
| 
 | ||||
|     def _send_ping(self, interval, event): | ||||
|         while not event.wait(interval): | ||||
|             self.last_ping_tm = time.time() | ||||
|             if self.sock: | ||||
|                 try: | ||||
|                     self.sock.ping() | ||||
|                 except Exception as ex: | ||||
|                     _logging.warning("send_ping routine terminated: {}".format(ex)) | ||||
|                     break | ||||
| 
 | ||||
|     def run_forever(self, sockopt=None, sslopt=None, | ||||
|                     ping_interval=0, ping_timeout=None, | ||||
|                     http_proxy_host=None, http_proxy_port=None, | ||||
|                     http_no_proxy=None, http_proxy_auth=None, | ||||
|                     skip_utf8_validation=False, | ||||
|                     host=None, origin=None, dispatcher=None, | ||||
|                     suppress_origin = False, proxy_type=None): | ||||
|         """ | ||||
|         run event loop for WebSocket framework. | ||||
|         This loop is infinite loop and is alive during websocket is available. | ||||
|         sockopt: values for socket.setsockopt. | ||||
|             sockopt must be tuple | ||||
|             and each element is argument of sock.setsockopt. | ||||
|         sslopt: ssl socket optional dict. | ||||
|         ping_interval: automatically send "ping" command | ||||
|             every specified period(second) | ||||
|             if set to 0, not send automatically. | ||||
|         ping_timeout: timeout(second) if the pong message is not received. | ||||
|         http_proxy_host: http proxy host name. | ||||
|         http_proxy_port: http proxy port. If not set, set to 80. | ||||
|         http_no_proxy: host names, which doesn't use proxy. | ||||
|         skip_utf8_validation: skip utf8 validation. | ||||
|         host: update host header. | ||||
|         origin: update origin header. | ||||
|         dispatcher: customize reading data from socket. | ||||
|         suppress_origin: suppress outputting origin header. | ||||
| 
 | ||||
|         Returns | ||||
|         ------- | ||||
|         False if caught KeyboardInterrupt | ||||
|         True if other exception was raised during a loop | ||||
|         """ | ||||
| 
 | ||||
|         if ping_timeout is not None and ping_timeout <= 0: | ||||
|             ping_timeout = None | ||||
|         if ping_timeout and ping_interval and ping_interval <= ping_timeout: | ||||
|             raise WebSocketException("Ensure ping_interval > ping_timeout") | ||||
|         if not sockopt: | ||||
|             sockopt = [] | ||||
|         if not sslopt: | ||||
|             sslopt = {} | ||||
|         if self.sock: | ||||
|             raise WebSocketException("socket is already opened") | ||||
|         thread = None | ||||
|         self.keep_running = True | ||||
|         self.last_ping_tm = 0 | ||||
|         self.last_pong_tm = 0 | ||||
| 
 | ||||
|         def teardown(close_frame=None): | ||||
|             """ | ||||
|             Tears down the connection. | ||||
|             If close_frame is set, we will invoke the on_close handler with the | ||||
|             statusCode and reason from there. | ||||
|             """ | ||||
|             if thread and thread.isAlive(): | ||||
|                 event.set() | ||||
|                 thread.join() | ||||
|             self.keep_running = False | ||||
|             if self.sock: | ||||
|                 self.sock.close() | ||||
|             close_args = self._get_close_args( | ||||
|                 close_frame.data if close_frame else None) | ||||
|             self._callback(self.on_close, *close_args) | ||||
|             self.sock = None | ||||
| 
 | ||||
|         try: | ||||
|             self.sock = WebSocket( | ||||
|                 self.get_mask_key, sockopt=sockopt, sslopt=sslopt, | ||||
|                 fire_cont_frame=self.on_cont_message is not None, | ||||
|                 skip_utf8_validation=skip_utf8_validation, | ||||
|                 enable_multithread=True if ping_interval else False) | ||||
|             self.sock.settimeout(getdefaulttimeout()) | ||||
|             self.sock.connect( | ||||
|                 self.url, header=self.header, cookie=self.cookie, | ||||
|                 http_proxy_host=http_proxy_host, | ||||
|                 http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy, | ||||
|                 http_proxy_auth=http_proxy_auth, subprotocols=self.subprotocols, | ||||
|                 host=host, origin=origin, suppress_origin=suppress_origin, | ||||
|                 proxy_type=proxy_type) | ||||
|             if not dispatcher: | ||||
|                 dispatcher = self.create_dispatcher(ping_timeout) | ||||
| 
 | ||||
|             self._callback(self.on_open) | ||||
| 
 | ||||
|             if ping_interval: | ||||
|                 event = threading.Event() | ||||
|                 thread = threading.Thread( | ||||
|                     target=self._send_ping, args=(ping_interval, event)) | ||||
|                 thread.setDaemon(True) | ||||
|                 thread.start() | ||||
| 
 | ||||
|             def read(): | ||||
|                 if not self.keep_running: | ||||
|                     return teardown() | ||||
| 
 | ||||
|                 op_code, frame = self.sock.recv_data_frame(True) | ||||
|                 if op_code == ABNF.OPCODE_CLOSE: | ||||
|                     return teardown(frame) | ||||
|                 elif op_code == ABNF.OPCODE_PING: | ||||
|                     self._callback(self.on_ping, frame.data) | ||||
|                 elif op_code == ABNF.OPCODE_PONG: | ||||
|                     self.last_pong_tm = time.time() | ||||
|                     self._callback(self.on_pong, frame.data) | ||||
|                 elif op_code == ABNF.OPCODE_CONT and self.on_cont_message: | ||||
|                     self._callback(self.on_data, frame.data, | ||||
|                                    frame.opcode, frame.fin) | ||||
|                     self._callback(self.on_cont_message, | ||||
|                                    frame.data, frame.fin) | ||||
|                 else: | ||||
|                     data = frame.data | ||||
|                     if six.PY3 and op_code == ABNF.OPCODE_TEXT: | ||||
|                         data = data.decode("utf-8") | ||||
|                     self._callback(self.on_data, data, frame.opcode, True) | ||||
|                     self._callback(self.on_message, data) | ||||
| 
 | ||||
|                 return True | ||||
| 
 | ||||
|             def check(): | ||||
|                 if (ping_timeout): | ||||
|                     has_timeout_expired = time.time() - self.last_ping_tm > ping_timeout | ||||
|                     has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0 | ||||
|                     has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > ping_timeout | ||||
| 
 | ||||
|                     if (self.last_ping_tm | ||||
|                             and has_timeout_expired | ||||
|                             and (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)): | ||||
|                         raise WebSocketTimeoutException("ping/pong timed out") | ||||
|                 return True | ||||
| 
 | ||||
|             dispatcher.read(self.sock.sock, read, check) | ||||
|         except (Exception, KeyboardInterrupt, SystemExit) as e: | ||||
|             self._callback(self.on_error, e) | ||||
|             if isinstance(e, SystemExit): | ||||
|                 # propagate SystemExit further | ||||
|                 raise | ||||
|             teardown() | ||||
|             return not isinstance(e, KeyboardInterrupt) | ||||
| 
 | ||||
|     def create_dispatcher(self, ping_timeout): | ||||
|         timeout = ping_timeout or 10 | ||||
|         if self.sock.is_ssl(): | ||||
|             return SSLDispacther(self, timeout) | ||||
| 
 | ||||
|         return Dispatcher(self, timeout) | ||||
| 
 | ||||
|     def _get_close_args(self, data): | ||||
|         """ this functions extracts the code, reason from the close body | ||||
|         if they exists, and if the self.on_close except three arguments """ | ||||
|         # if the on_close callback is "old", just return empty list | ||||
|         if sys.version_info < (3, 0): | ||||
|             if not self.on_close or len(inspect.getargspec(self.on_close).args) != 3: | ||||
|                 return [] | ||||
|         else: | ||||
|             if not self.on_close or len(inspect.getfullargspec(self.on_close).args) != 3: | ||||
|                 return [] | ||||
| 
 | ||||
|         if data and len(data) >= 2: | ||||
|             code = 256 * six.byte2int(data[0:1]) + six.byte2int(data[1:2]) | ||||
|             reason = data[2:].decode('utf-8') | ||||
|             return [code, reason] | ||||
| 
 | ||||
|         return [None, None] | ||||
| 
 | ||||
|     def _callback(self, callback, *args): | ||||
|         if callback: | ||||
|             try: | ||||
|                 if inspect.ismethod(callback): | ||||
|                     callback(*args) | ||||
|                 else: | ||||
|                     callback(self, *args) | ||||
| 
 | ||||
|             except Exception as e: | ||||
|                 _logging.error("error from callback {}: {}".format(callback, e)) | ||||
|                 if _logging.isEnabledForDebug(): | ||||
|                     _, _, tb = sys.exc_info() | ||||
|                     traceback.print_tb(tb) | ||||
| @ -0,0 +1,52 @@ | ||||
| try: | ||||
|     import Cookie | ||||
| except: | ||||
|     import http.cookies as Cookie | ||||
| 
 | ||||
| 
 | ||||
| class SimpleCookieJar(object): | ||||
|     def __init__(self): | ||||
|         self.jar = dict() | ||||
| 
 | ||||
|     def add(self, set_cookie): | ||||
|         if set_cookie: | ||||
|             try: | ||||
|                 simpleCookie = Cookie.SimpleCookie(set_cookie) | ||||
|             except: | ||||
|                 simpleCookie = Cookie.SimpleCookie(set_cookie.encode('ascii', 'ignore')) | ||||
| 
 | ||||
|             for k, v in simpleCookie.items(): | ||||
|                 domain = v.get("domain") | ||||
|                 if domain: | ||||
|                     if not domain.startswith("."): | ||||
|                         domain = "." + domain | ||||
|                     cookie = self.jar.get(domain) if self.jar.get(domain) else Cookie.SimpleCookie() | ||||
|                     cookie.update(simpleCookie) | ||||
|                     self.jar[domain.lower()] = cookie | ||||
| 
 | ||||
|     def set(self, set_cookie): | ||||
|         if set_cookie: | ||||
|             try: | ||||
|                 simpleCookie = Cookie.SimpleCookie(set_cookie) | ||||
|             except: | ||||
|                 simpleCookie = Cookie.SimpleCookie(set_cookie.encode('ascii', 'ignore')) | ||||
| 
 | ||||
|             for k, v in simpleCookie.items(): | ||||
|                 domain = v.get("domain") | ||||
|                 if domain: | ||||
|                     if not domain.startswith("."): | ||||
|                         domain = "." + domain | ||||
|                     self.jar[domain.lower()] = simpleCookie | ||||
| 
 | ||||
|     def get(self, host): | ||||
|         if not host: | ||||
|             return "" | ||||
| 
 | ||||
|         cookies = [] | ||||
|         for domain, simpleCookie in self.jar.items(): | ||||
|             host = host.lower() | ||||
|             if host.endswith(domain) or host == domain[1:]: | ||||
|                 cookies.append(self.jar.get(domain)) | ||||
| 
 | ||||
|         return "; ".join(filter(None, ["%s=%s" % (k, v.value) for cookie in filter(None, sorted(cookies)) for k, v in | ||||
|                                        sorted(cookie.items())])) | ||||
| @ -0,0 +1,515 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA  02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| from __future__ import print_function | ||||
| 
 | ||||
| import socket | ||||
| import struct | ||||
| import threading | ||||
| import time | ||||
| 
 | ||||
| import six | ||||
| 
 | ||||
| # websocket modules | ||||
| from ._abnf import * | ||||
| from ._exceptions import * | ||||
| from ._handshake import * | ||||
| from ._http import * | ||||
| from ._logging import * | ||||
| from ._socket import * | ||||
| from ._ssl_compat import * | ||||
| from ._utils import * | ||||
| 
 | ||||
| __all__ = ['WebSocket', 'create_connection'] | ||||
| 
 | ||||
| """ | ||||
| websocket python client. | ||||
| ========================= | ||||
| 
 | ||||
| This version support only hybi-13. | ||||
| Please see http://tools.ietf.org/html/rfc6455 for protocol. | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| class WebSocket(object): | ||||
|     """ | ||||
|     Low level WebSocket interface. | ||||
|     This class is based on | ||||
|       The WebSocket protocol draft-hixie-thewebsocketprotocol-76 | ||||
|       http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 | ||||
| 
 | ||||
|     We can connect to the websocket server and send/receive data. | ||||
|     The following example is an echo client. | ||||
| 
 | ||||
|     >>> import websocket | ||||
|     >>> ws = websocket.WebSocket() | ||||
|     >>> ws.connect("ws://echo.websocket.org") | ||||
|     >>> ws.send("Hello, Server") | ||||
|     >>> ws.recv() | ||||
|     'Hello, Server' | ||||
|     >>> ws.close() | ||||
| 
 | ||||
|     get_mask_key: a callable to produce new mask keys, see the set_mask_key | ||||
|       function's docstring for more details | ||||
|     sockopt: values for socket.setsockopt. | ||||
|         sockopt must be tuple and each element is argument of sock.setsockopt. | ||||
|     sslopt: dict object for ssl socket option. | ||||
|     fire_cont_frame: fire recv event for each cont frame. default is False | ||||
|     enable_multithread: if set to True, lock send method. | ||||
|     skip_utf8_validation: skip utf8 validation. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, get_mask_key=None, sockopt=None, sslopt=None, | ||||
|                  fire_cont_frame=False, enable_multithread=False, | ||||
|                  skip_utf8_validation=False, **_): | ||||
|         """ | ||||
|         Initialize WebSocket object. | ||||
|         """ | ||||
|         self.sock_opt = sock_opt(sockopt, sslopt) | ||||
|         self.handshake_response = None | ||||
|         self.sock = None | ||||
| 
 | ||||
|         self.connected = False | ||||
|         self.get_mask_key = get_mask_key | ||||
|         # These buffer over the build-up of a single frame. | ||||
|         self.frame_buffer = frame_buffer(self._recv, skip_utf8_validation) | ||||
|         self.cont_frame = continuous_frame( | ||||
|             fire_cont_frame, skip_utf8_validation) | ||||
| 
 | ||||
|         if enable_multithread: | ||||
|             self.lock = threading.Lock() | ||||
|             self.readlock = threading.Lock() | ||||
|         else: | ||||
|             self.lock = NoLock() | ||||
|             self.readlock = NoLock() | ||||
| 
 | ||||
|     def __iter__(self): | ||||
|         """ | ||||
|         Allow iteration over websocket, implying sequential `recv` executions. | ||||
|         """ | ||||
|         while True: | ||||
|             yield self.recv() | ||||
| 
 | ||||
|     def __next__(self): | ||||
|         return self.recv() | ||||
| 
 | ||||
|     def next(self): | ||||
|         return self.__next__() | ||||
| 
 | ||||
|     def fileno(self): | ||||
|         return self.sock.fileno() | ||||
| 
 | ||||
|     def set_mask_key(self, func): | ||||
|         """ | ||||
|         set function to create musk key. You can customize mask key generator. | ||||
|         Mainly, this is for testing purpose. | ||||
| 
 | ||||
|         func: callable object. the func takes 1 argument as integer. | ||||
|               The argument means length of mask key. | ||||
|               This func must return string(byte array), | ||||
|               which length is argument specified. | ||||
|         """ | ||||
|         self.get_mask_key = func | ||||
| 
 | ||||
|     def gettimeout(self): | ||||
|         """ | ||||
|         Get the websocket timeout(second). | ||||
|         """ | ||||
|         return self.sock_opt.timeout | ||||
| 
 | ||||
|     def settimeout(self, timeout): | ||||
|         """ | ||||
|         Set the timeout to the websocket. | ||||
| 
 | ||||
|         timeout: timeout time(second). | ||||
|         """ | ||||
|         self.sock_opt.timeout = timeout | ||||
|         if self.sock: | ||||
|             self.sock.settimeout(timeout) | ||||
| 
 | ||||
|     timeout = property(gettimeout, settimeout) | ||||
| 
 | ||||
|     def getsubprotocol(self): | ||||
|         """ | ||||
|         get subprotocol | ||||
|         """ | ||||
|         if self.handshake_response: | ||||
|             return self.handshake_response.subprotocol | ||||
|         else: | ||||
|             return None | ||||
| 
 | ||||
|     subprotocol = property(getsubprotocol) | ||||
| 
 | ||||
|     def getstatus(self): | ||||
|         """ | ||||
|         get handshake status | ||||
|         """ | ||||
|         if self.handshake_response: | ||||
|             return self.handshake_response.status | ||||
|         else: | ||||
|             return None | ||||
| 
 | ||||
|     status = property(getstatus) | ||||
| 
 | ||||
|     def getheaders(self): | ||||
|         """ | ||||
|         get handshake response header | ||||
|         """ | ||||
|         if self.handshake_response: | ||||
|             return self.handshake_response.headers | ||||
|         else: | ||||
|             return None | ||||
| 
 | ||||
|     def is_ssl(self): | ||||
|         return isinstance(self.sock, ssl.SSLSocket) | ||||
| 
 | ||||
|     headers = property(getheaders) | ||||
| 
 | ||||
|     def connect(self, url, **options): | ||||
|         """ | ||||
|         Connect to url. url is websocket url scheme. | ||||
|         ie. ws://host:port/resource | ||||
|         You can customize using 'options'. | ||||
|         If you set "header" list object, you can set your own custom header. | ||||
| 
 | ||||
|         >>> ws = WebSocket() | ||||
|         >>> ws.connect("ws://echo.websocket.org/", | ||||
|                 ...     header=["User-Agent: MyProgram", | ||||
|                 ...             "x-custom: header"]) | ||||
| 
 | ||||
|         timeout: socket timeout time. This value is integer. | ||||
|                  if you set None for this value, | ||||
|                  it means "use default_timeout value" | ||||
| 
 | ||||
|         options: "header" -> custom http header list or dict. | ||||
|                  "cookie" -> cookie value. | ||||
|                  "origin" -> custom origin url. | ||||
|                  "suppress_origin" -> suppress outputting origin header. | ||||
|                  "host"   -> custom host header string. | ||||
|                  "http_proxy_host" - http proxy host name. | ||||
|                  "http_proxy_port" - http proxy port. If not set, set to 80. | ||||
|                  "http_no_proxy"   - host names, which doesn't use proxy. | ||||
|                  "http_proxy_auth" - http proxy auth information. | ||||
|                                      tuple of username and password. | ||||
|                                      default is None | ||||
|                  "redirect_limit" -> number of redirects to follow. | ||||
|                  "subprotocols" - array of available sub protocols. | ||||
|                                   default is None. | ||||
|                  "socket" - pre-initialized stream socket. | ||||
| 
 | ||||
|         """ | ||||
|         # FIXME: "subprotocols" are getting lost, not passed down | ||||
|         # FIXME: "header", "cookie", "origin" and "host" too | ||||
|         self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout) | ||||
|         self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), | ||||
|                                    options.pop('socket', None)) | ||||
| 
 | ||||
|         try: | ||||
|             self.handshake_response = handshake(self.sock, *addrs, **options) | ||||
|             for attempt in range(options.pop('redirect_limit', 3)): | ||||
|                 if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES: | ||||
|                     url = self.handshake_response.headers['location'] | ||||
|                     self.sock.close() | ||||
|                     self.sock, addrs =  connect(url, self.sock_opt, proxy_info(**options), | ||||
|                                                 options.pop('socket', None)) | ||||
|                     self.handshake_response = handshake(self.sock, *addrs, **options) | ||||
|             self.connected = True | ||||
|         except: | ||||
|             if self.sock: | ||||
|                 self.sock.close() | ||||
|                 self.sock = None | ||||
|             raise | ||||
| 
 | ||||
|     def send(self, payload, opcode=ABNF.OPCODE_TEXT): | ||||
|         """ | ||||
|         Send the data as string. | ||||
| 
 | ||||
|         payload: Payload must be utf-8 string or unicode, | ||||
|                   if the opcode is OPCODE_TEXT. | ||||
|                   Otherwise, it must be string(byte array) | ||||
| 
 | ||||
|         opcode: operation code to send. Please see OPCODE_XXX. | ||||
|         """ | ||||
| 
 | ||||
|         frame = ABNF.create_frame(payload, opcode) | ||||
|         return self.send_frame(frame) | ||||
| 
 | ||||
|     def send_frame(self, frame): | ||||
|         """ | ||||
|         Send the data frame. | ||||
| 
 | ||||
|         frame: frame data created  by ABNF.create_frame | ||||
| 
 | ||||
|         >>> ws = create_connection("ws://echo.websocket.org/") | ||||
|         >>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT) | ||||
|         >>> ws.send_frame(frame) | ||||
|         >>> cont_frame = ABNF.create_frame("My name is ", ABNF.OPCODE_CONT, 0) | ||||
|         >>> ws.send_frame(frame) | ||||
|         >>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1) | ||||
|         >>> ws.send_frame(frame) | ||||
| 
 | ||||
|         """ | ||||
|         if self.get_mask_key: | ||||
|             frame.get_mask_key = self.get_mask_key | ||||
|         data = frame.format() | ||||
|         length = len(data) | ||||
|         trace("send: " + repr(data)) | ||||
| 
 | ||||
|         with self.lock: | ||||
|             while data: | ||||
|                 l = self._send(data) | ||||
|                 data = data[l:] | ||||
| 
 | ||||
|         return length | ||||
| 
 | ||||
|     def send_binary(self, payload): | ||||
|         return self.send(payload, ABNF.OPCODE_BINARY) | ||||
| 
 | ||||
|     def ping(self, payload=""): | ||||
|         """ | ||||
|         send ping data. | ||||
| 
 | ||||
|         payload: data payload to send server. | ||||
|         """ | ||||
|         if isinstance(payload, six.text_type): | ||||
|             payload = payload.encode("utf-8") | ||||
|         self.send(payload, ABNF.OPCODE_PING) | ||||
| 
 | ||||
|     def pong(self, payload): | ||||
|         """ | ||||
|         send pong data. | ||||
| 
 | ||||
|         payload: data payload to send server. | ||||
|         """ | ||||
|         if isinstance(payload, six.text_type): | ||||
|             payload = payload.encode("utf-8") | ||||
|         self.send(payload, ABNF.OPCODE_PONG) | ||||
| 
 | ||||
|     def recv(self): | ||||
|         """ | ||||
|         Receive string data(byte array) from the server. | ||||
| 
 | ||||
|         return value: string(byte array) value. | ||||
|         """ | ||||
|         with self.readlock: | ||||
|             opcode, data = self.recv_data() | ||||
|         if six.PY3 and opcode == ABNF.OPCODE_TEXT: | ||||
|             return data.decode("utf-8") | ||||
|         elif opcode == ABNF.OPCODE_TEXT or opcode == ABNF.OPCODE_BINARY: | ||||
|             return data | ||||
|         else: | ||||
|             return '' | ||||
| 
 | ||||
|     def recv_data(self, control_frame=False): | ||||
|         """ | ||||
|         Receive data with operation code. | ||||
| 
 | ||||
|         control_frame: a boolean flag indicating whether to return control frame | ||||
|         data, defaults to False | ||||
| 
 | ||||
|         return  value: tuple of operation code and string(byte array) value. | ||||
|         """ | ||||
|         opcode, frame = self.recv_data_frame(control_frame) | ||||
|         return opcode, frame.data | ||||
| 
 | ||||
|     def recv_data_frame(self, control_frame=False): | ||||
|         """ | ||||
|         Receive data with operation code. | ||||
| 
 | ||||
|         control_frame: a boolean flag indicating whether to return control frame | ||||
|         data, defaults to False | ||||
| 
 | ||||
|         return  value: tuple of operation code and string(byte array) value. | ||||
|         """ | ||||
|         while True: | ||||
|             frame = self.recv_frame() | ||||
|             if not frame: | ||||
|                 # handle error: | ||||
|                 # 'NoneType' object has no attribute 'opcode' | ||||
|                 raise WebSocketProtocolException( | ||||
|                     "Not a valid frame %s" % frame) | ||||
|             elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT): | ||||
|                 self.cont_frame.validate(frame) | ||||
|                 self.cont_frame.add(frame) | ||||
| 
 | ||||
|                 if self.cont_frame.is_fire(frame): | ||||
|                     return self.cont_frame.extract(frame) | ||||
| 
 | ||||
|             elif frame.opcode == ABNF.OPCODE_CLOSE: | ||||
|                 self.send_close() | ||||
|                 return frame.opcode, frame | ||||
|             elif frame.opcode == ABNF.OPCODE_PING: | ||||
|                 if len(frame.data) < 126: | ||||
|                     self.pong(frame.data) | ||||
|                 else: | ||||
|                     raise WebSocketProtocolException( | ||||
|                         "Ping message is too long") | ||||
|                 if control_frame: | ||||
|                     return frame.opcode, frame | ||||
|             elif frame.opcode == ABNF.OPCODE_PONG: | ||||
|                 if control_frame: | ||||
|                     return frame.opcode, frame | ||||
| 
 | ||||
|     def recv_frame(self): | ||||
|         """ | ||||
|         receive data as frame from server. | ||||
| 
 | ||||
|         return value: ABNF frame object. | ||||
|         """ | ||||
|         return self.frame_buffer.recv_frame() | ||||
| 
 | ||||
|     def send_close(self, status=STATUS_NORMAL, reason=six.b("")): | ||||
|         """ | ||||
|         send close data to the server. | ||||
| 
 | ||||
|         status: status code to send. see STATUS_XXX. | ||||
| 
 | ||||
|         reason: the reason to close. This must be string or bytes. | ||||
|         """ | ||||
|         if status < 0 or status >= ABNF.LENGTH_16: | ||||
|             raise ValueError("code is invalid range") | ||||
|         self.connected = False | ||||
|         self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) | ||||
| 
 | ||||
|     def close(self, status=STATUS_NORMAL, reason=six.b(""), timeout=3): | ||||
|         """ | ||||
|         Close Websocket object | ||||
| 
 | ||||
|         status: status code to send. see STATUS_XXX. | ||||
| 
 | ||||
|         reason: the reason to close. This must be string. | ||||
| 
 | ||||
|         timeout: timeout until receive a close frame. | ||||
|             If None, it will wait forever until receive a close frame. | ||||
|         """ | ||||
|         if self.connected: | ||||
|             if status < 0 or status >= ABNF.LENGTH_16: | ||||
|                 raise ValueError("code is invalid range") | ||||
| 
 | ||||
|             try: | ||||
|                 self.connected = False | ||||
|                 self.send(struct.pack('!H', status) + | ||||
|                           reason, ABNF.OPCODE_CLOSE) | ||||
|                 sock_timeout = self.sock.gettimeout() | ||||
|                 self.sock.settimeout(timeout) | ||||
|                 start_time = time.time() | ||||
|                 while timeout is None or time.time() - start_time < timeout: | ||||
|                     try: | ||||
|                         frame = self.recv_frame() | ||||
|                         if frame.opcode != ABNF.OPCODE_CLOSE: | ||||
|                             continue | ||||
|                         if isEnabledForError(): | ||||
|                             recv_status = struct.unpack("!H", frame.data[0:2])[0] | ||||
|                             if recv_status != STATUS_NORMAL: | ||||
|                                 error("close status: " + repr(recv_status)) | ||||
|                         break | ||||
|                     except: | ||||
|                         break | ||||
|                 self.sock.settimeout(sock_timeout) | ||||
|                 self.sock.shutdown(socket.SHUT_RDWR) | ||||
|             except: | ||||
|                 pass | ||||
| 
 | ||||
|             self.shutdown() | ||||
| 
 | ||||
|     def abort(self): | ||||
|         """ | ||||
|         Low-level asynchronous abort, wakes up other threads that are waiting in recv_* | ||||
|         """ | ||||
|         if self.connected: | ||||
|             self.sock.shutdown(socket.SHUT_RDWR) | ||||
| 
 | ||||
|     def shutdown(self): | ||||
|         """close socket, immediately.""" | ||||
|         if self.sock: | ||||
|             self.sock.close() | ||||
|             self.sock = None | ||||
|             self.connected = False | ||||
| 
 | ||||
|     def _send(self, data): | ||||
|         return send(self.sock, data) | ||||
| 
 | ||||
|     def _recv(self, bufsize): | ||||
|         try: | ||||
|             return recv(self.sock, bufsize) | ||||
|         except WebSocketConnectionClosedException: | ||||
|             if self.sock: | ||||
|                 self.sock.close() | ||||
|             self.sock = None | ||||
|             self.connected = False | ||||
|             raise | ||||
| 
 | ||||
| 
 | ||||
| def create_connection(url, timeout=None, class_=WebSocket, **options): | ||||
|     """ | ||||
|     connect to url and return websocket object. | ||||
| 
 | ||||
|     Connect to url and return the WebSocket object. | ||||
|     Passing optional timeout parameter will set the timeout on the socket. | ||||
|     If no timeout is supplied, | ||||
|     the global default timeout setting returned by getdefauttimeout() is used. | ||||
|     You can customize using 'options'. | ||||
|     If you set "header" list object, you can set your own custom header. | ||||
| 
 | ||||
|     >>> conn = create_connection("ws://echo.websocket.org/", | ||||
|          ...     header=["User-Agent: MyProgram", | ||||
|          ...             "x-custom: header"]) | ||||
| 
 | ||||
| 
 | ||||
|     timeout: socket timeout time. This value is integer. | ||||
|              if you set None for this value, | ||||
|              it means "use default_timeout value" | ||||
| 
 | ||||
|     class_: class to instantiate when creating the connection. It has to implement | ||||
|             settimeout and connect. It's __init__ should be compatible with | ||||
|             WebSocket.__init__, i.e. accept all of it's kwargs. | ||||
|     options: "header" -> custom http header list or dict. | ||||
|              "cookie" -> cookie value. | ||||
|              "origin" -> custom origin url. | ||||
|              "suppress_origin" -> suppress outputting origin header. | ||||
|              "host"   -> custom host header string. | ||||
|              "http_proxy_host" - http proxy host name. | ||||
|              "http_proxy_port" - http proxy port. If not set, set to 80. | ||||
|              "http_no_proxy"   - host names, which doesn't use proxy. | ||||
|              "http_proxy_auth" - http proxy auth information. | ||||
|                                     tuple of username and password. | ||||
|                                     default is None | ||||
|              "enable_multithread" -> enable lock for multithread. | ||||
|              "redirect_limit" -> number of redirects to follow. | ||||
|              "sockopt" -> socket options | ||||
|              "sslopt" -> ssl option | ||||
|              "subprotocols" - array of available sub protocols. | ||||
|                               default is None. | ||||
|              "skip_utf8_validation" - skip utf8 validation. | ||||
|              "socket" - pre-initialized stream socket. | ||||
|     """ | ||||
|     sockopt = options.pop("sockopt", []) | ||||
|     sslopt = options.pop("sslopt", {}) | ||||
|     fire_cont_frame = options.pop("fire_cont_frame", False) | ||||
|     enable_multithread = options.pop("enable_multithread", False) | ||||
|     skip_utf8_validation = options.pop("skip_utf8_validation", False) | ||||
|     websock = class_(sockopt=sockopt, sslopt=sslopt, | ||||
|                      fire_cont_frame=fire_cont_frame, | ||||
|                      enable_multithread=enable_multithread, | ||||
|                      skip_utf8_validation=skip_utf8_validation, **options) | ||||
|     websock.settimeout(timeout if timeout is not None else getdefaulttimeout()) | ||||
|     websock.connect(url, **options) | ||||
|     return websock | ||||
| @ -0,0 +1,87 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA  02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| """ | ||||
| define websocket exceptions | ||||
| """ | ||||
| 
 | ||||
| 
 | ||||
| class WebSocketException(Exception): | ||||
|     """ | ||||
|     websocket exception class. | ||||
|     """ | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class WebSocketProtocolException(WebSocketException): | ||||
|     """ | ||||
|     If the websocket protocol is invalid, this exception will be raised. | ||||
|     """ | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class WebSocketPayloadException(WebSocketException): | ||||
|     """ | ||||
|     If the websocket payload is invalid, this exception will be raised. | ||||
|     """ | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class WebSocketConnectionClosedException(WebSocketException): | ||||
|     """ | ||||
|     If remote host closed the connection or some network error happened, | ||||
|     this exception will be raised. | ||||
|     """ | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class WebSocketTimeoutException(WebSocketException): | ||||
|     """ | ||||
|     WebSocketTimeoutException will be raised at socket timeout during read/write data. | ||||
|     """ | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class WebSocketProxyException(WebSocketException): | ||||
|     """ | ||||
|     WebSocketProxyException will be raised when proxy error occurred. | ||||
|     """ | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class WebSocketBadStatusException(WebSocketException): | ||||
|     """ | ||||
|     WebSocketBadStatusException will be raised when we get bad handshake status code. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, message, status_code, status_message=None, resp_headers=None): | ||||
|         msg = message % (status_code, status_message) | ||||
|         super(WebSocketBadStatusException, self).__init__(msg) | ||||
|         self.status_code = status_code | ||||
|         self.resp_headers = resp_headers | ||||
| 
 | ||||
| class WebSocketAddressException(WebSocketException): | ||||
|     """ | ||||
|     If the websocket address info cannot be found, this exception will be raised. | ||||
|     """ | ||||
|     pass | ||||
| @ -0,0 +1,205 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA  02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| import hashlib | ||||
| import hmac | ||||
| import os | ||||
| 
 | ||||
| import six | ||||
| 
 | ||||
| from ._cookiejar import SimpleCookieJar | ||||
| from ._exceptions import * | ||||
| from ._http import * | ||||
| from ._logging import * | ||||
| from ._socket import * | ||||
| 
 | ||||
| if six.PY3: | ||||
|     from base64 import encodebytes as base64encode | ||||
| else: | ||||
|     from base64 import encodestring as base64encode | ||||
| 
 | ||||
| if six.PY3: | ||||
|     if six.PY34: | ||||
|         from http import client as HTTPStatus | ||||
|     else: | ||||
|         from http import HTTPStatus | ||||
| else: | ||||
|     import httplib as HTTPStatus | ||||
| 
 | ||||
| __all__ = ["handshake_response", "handshake", "SUPPORTED_REDIRECT_STATUSES"] | ||||
| 
 | ||||
| if hasattr(hmac, "compare_digest"): | ||||
|     compare_digest = hmac.compare_digest | ||||
| else: | ||||
|     def compare_digest(s1, s2): | ||||
|         return s1 == s2 | ||||
| 
 | ||||
| # websocket supported version. | ||||
| VERSION = 13 | ||||
| 
 | ||||
| SUPPORTED_REDIRECT_STATUSES = [HTTPStatus.MOVED_PERMANENTLY, HTTPStatus.FOUND, HTTPStatus.SEE_OTHER] | ||||
| 
 | ||||
| CookieJar = SimpleCookieJar() | ||||
| 
 | ||||
| 
 | ||||
| class handshake_response(object): | ||||
| 
 | ||||
|     def __init__(self, status, headers, subprotocol): | ||||
|         self.status = status | ||||
|         self.headers = headers | ||||
|         self.subprotocol = subprotocol | ||||
|         CookieJar.add(headers.get("set-cookie")) | ||||
| 
 | ||||
| 
 | ||||
| def handshake(sock, hostname, port, resource, **options): | ||||
|     headers, key = _get_handshake_headers(resource, hostname, port, options) | ||||
| 
 | ||||
|     header_str = "\r\n".join(headers) | ||||
|     send(sock, header_str) | ||||
|     dump("request header", header_str) | ||||
| 
 | ||||
|     status, resp = _get_resp_headers(sock) | ||||
|     if status in SUPPORTED_REDIRECT_STATUSES: | ||||
|         return handshake_response(status, resp, None) | ||||
|     success, subproto = _validate(resp, key, options.get("subprotocols")) | ||||
|     if not success: | ||||
|         raise WebSocketException("Invalid WebSocket Header") | ||||
| 
 | ||||
|     return handshake_response(status, resp, subproto) | ||||
| 
 | ||||
| def _pack_hostname(hostname): | ||||
|     # IPv6 address | ||||
|     if ':' in hostname: | ||||
|         return '[' + hostname + ']' | ||||
| 
 | ||||
|     return hostname | ||||
| 
 | ||||
| def _get_handshake_headers(resource, host, port, options): | ||||
|     headers = [ | ||||
|         "GET %s HTTP/1.1" % resource, | ||||
|         "Upgrade: websocket", | ||||
|         "Connection: Upgrade" | ||||
|     ] | ||||
|     if port == 80 or port == 443: | ||||
|         hostport = _pack_hostname(host) | ||||
|     else: | ||||
|         hostport = "%s:%d" % (_pack_hostname(host), port) | ||||
| 
 | ||||
|     if "host" in options and options["host"] is not None: | ||||
|         headers.append("Host: %s" % options["host"]) | ||||
|     else: | ||||
|         headers.append("Host: %s" % hostport) | ||||
| 
 | ||||
|     if "suppress_origin" not in options or not options["suppress_origin"]: | ||||
|         if "origin" in options and options["origin"] is not None: | ||||
|             headers.append("Origin: %s" % options["origin"]) | ||||
|         else: | ||||
|             headers.append("Origin: http://%s" % hostport) | ||||
| 
 | ||||
|     key = _create_sec_websocket_key() | ||||
|      | ||||
|     # Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified | ||||
|     if not 'header' in options or 'Sec-WebSocket-Key' not in options['header']: | ||||
|         key = _create_sec_websocket_key() | ||||
|         headers.append("Sec-WebSocket-Key: %s" % key) | ||||
|     else: | ||||
|         key = options['header']['Sec-WebSocket-Key'] | ||||
| 
 | ||||
|     if not 'header' in options or 'Sec-WebSocket-Version' not in options['header']: | ||||
|         headers.append("Sec-WebSocket-Version: %s" % VERSION) | ||||
| 
 | ||||
|     subprotocols = options.get("subprotocols") | ||||
|     if subprotocols: | ||||
|         headers.append("Sec-WebSocket-Protocol: %s" % ",".join(subprotocols)) | ||||
| 
 | ||||
|     if "header" in options: | ||||
|         header = options["header"] | ||||
|         if isinstance(header, dict): | ||||
|             header = [ | ||||
|                 ": ".join([k, v]) | ||||
|                 for k, v in header.items() | ||||
|                 if v is not None | ||||
|             ] | ||||
|         headers.extend(header) | ||||
| 
 | ||||
|     server_cookie = CookieJar.get(host) | ||||
|     client_cookie = options.get("cookie", None) | ||||
| 
 | ||||
|     cookie = "; ".join(filter(None, [server_cookie, client_cookie])) | ||||
| 
 | ||||
|     if cookie: | ||||
|         headers.append("Cookie: %s" % cookie) | ||||
| 
 | ||||
|     headers.append("") | ||||
|     headers.append("") | ||||
| 
 | ||||
|     return headers, key | ||||
| 
 | ||||
| 
 | ||||
| def _get_resp_headers(sock, success_statuses=(101, 301, 302, 303)): | ||||
|     status, resp_headers, status_message = read_headers(sock) | ||||
|     if status not in success_statuses: | ||||
|         raise WebSocketBadStatusException("Handshake status %d %s", status, status_message, resp_headers) | ||||
|     return status, resp_headers | ||||
| 
 | ||||
| _HEADERS_TO_CHECK = { | ||||
|     "upgrade": "websocket", | ||||
|     "connection": "upgrade", | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| def _validate(headers, key, subprotocols): | ||||
|     subproto = None | ||||
|     for k, v in _HEADERS_TO_CHECK.items(): | ||||
|         r = headers.get(k, None) | ||||
|         if not r: | ||||
|             return False, None | ||||
|         r = r.lower() | ||||
|         if v != r: | ||||
|             return False, None | ||||
| 
 | ||||
|     if subprotocols: | ||||
|         subproto = headers.get("sec-websocket-protocol", None).lower() | ||||
|         if not subproto or subproto not in [s.lower() for s in subprotocols]: | ||||
|             error("Invalid subprotocol: " + str(subprotocols)) | ||||
|             return False, None | ||||
| 
 | ||||
|     result = headers.get("sec-websocket-accept", None) | ||||
|     if not result: | ||||
|         return False, None | ||||
|     result = result.lower() | ||||
| 
 | ||||
|     if isinstance(result, six.text_type): | ||||
|         result = result.encode('utf-8') | ||||
| 
 | ||||
|     value = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode('utf-8') | ||||
|     hashed = base64encode(hashlib.sha1(value).digest()).strip().lower() | ||||
|     success = compare_digest(hashed, result) | ||||
| 
 | ||||
|     if success: | ||||
|         return True, subproto | ||||
|     else: | ||||
|         return False, None | ||||
| 
 | ||||
| 
 | ||||
| def _create_sec_websocket_key(): | ||||
|     randomness = os.urandom(16) | ||||
|     return base64encode(randomness).decode('utf-8').strip() | ||||
| @ -0,0 +1,328 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA  02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| import errno | ||||
| import os | ||||
| import socket | ||||
| import sys | ||||
| 
 | ||||
| import six | ||||
| 
 | ||||
| from ._exceptions import * | ||||
| from ._logging import * | ||||
| from ._socket import* | ||||
| from ._ssl_compat import * | ||||
| from ._url import * | ||||
| 
 | ||||
| if six.PY3: | ||||
|     from base64 import encodebytes as base64encode | ||||
| else: | ||||
|     from base64 import encodestring as base64encode | ||||
| 
 | ||||
| __all__ = ["proxy_info", "connect", "read_headers"] | ||||
| 
 | ||||
| try: | ||||
|     import socks | ||||
|     ProxyConnectionError = socks.ProxyConnectionError | ||||
|     HAS_PYSOCKS = True | ||||
| except: | ||||
|     class ProxyConnectionError(BaseException): | ||||
|         pass | ||||
|     HAS_PYSOCKS = False | ||||
| 
 | ||||
| class proxy_info(object): | ||||
| 
 | ||||
|     def __init__(self, **options): | ||||
|         self.type = options.get("proxy_type") or "http" | ||||
|         if not(self.type in ['http', 'socks4', 'socks5', 'socks5h']): | ||||
|             raise ValueError("proxy_type must be 'http', 'socks4', 'socks5' or 'socks5h'") | ||||
|         self.host = options.get("http_proxy_host", None) | ||||
|         if self.host: | ||||
|             self.port = options.get("http_proxy_port", 0) | ||||
|             self.auth = options.get("http_proxy_auth", None) | ||||
|             self.no_proxy = options.get("http_no_proxy", None) | ||||
|         else: | ||||
|             self.port = 0 | ||||
|             self.auth = None | ||||
|             self.no_proxy = None | ||||
| 
 | ||||
| def _open_proxied_socket(url, options, proxy): | ||||
|     hostname, port, resource, is_secure = parse_url(url) | ||||
| 
 | ||||
|     if not HAS_PYSOCKS: | ||||
|         raise WebSocketException("PySocks module not found.") | ||||
| 
 | ||||
|     ptype = socks.SOCKS5 | ||||
|     rdns = False | ||||
|     if proxy.type == "socks4": | ||||
|         ptype = socks.SOCKS4 | ||||
|     if proxy.type == "http": | ||||
|         ptype = socks.HTTP | ||||
|     if proxy.type[-1] == "h": | ||||
|         rdns = True | ||||
| 
 | ||||
|     sock = socks.create_connection( | ||||
|             (hostname, port), | ||||
|             proxy_type = ptype, | ||||
|             proxy_addr = proxy.host, | ||||
|             proxy_port = proxy.port, | ||||
|             proxy_rdns = rdns, | ||||
|             proxy_username = proxy.auth[0] if proxy.auth else None, | ||||
|             proxy_password = proxy.auth[1] if proxy.auth else None, | ||||
|             timeout = options.timeout, | ||||
|             socket_options = DEFAULT_SOCKET_OPTION + options.sockopt | ||||
|     ) | ||||
| 
 | ||||
|     if is_secure: | ||||
|         if HAVE_SSL: | ||||
|             sock = _ssl_socket(sock, options.sslopt, hostname) | ||||
|         else: | ||||
|             raise WebSocketException("SSL not available.") | ||||
| 
 | ||||
|     return sock, (hostname, port, resource) | ||||
| 
 | ||||
| 
 | ||||
| def connect(url, options, proxy, socket): | ||||
|     if proxy.host and not socket and not (proxy.type == 'http'): | ||||
|         return _open_proxied_socket(url, options, proxy) | ||||
| 
 | ||||
|     hostname, port, resource, is_secure = parse_url(url) | ||||
| 
 | ||||
|     if socket: | ||||
|         return socket, (hostname, port, resource) | ||||
| 
 | ||||
|     addrinfo_list, need_tunnel, auth = _get_addrinfo_list( | ||||
|         hostname, port, is_secure, proxy) | ||||
|     if not addrinfo_list: | ||||
|         raise WebSocketException( | ||||
|             "Host not found.: " + hostname + ":" + str(port)) | ||||
| 
 | ||||
|     sock = None | ||||
|     try: | ||||
|         sock = _open_socket(addrinfo_list, options.sockopt, options.timeout) | ||||
|         if need_tunnel: | ||||
|             sock = _tunnel(sock, hostname, port, auth) | ||||
| 
 | ||||
|         if is_secure: | ||||
|             if HAVE_SSL: | ||||
|                 sock = _ssl_socket(sock, options.sslopt, hostname) | ||||
|             else: | ||||
|                 raise WebSocketException("SSL not available.") | ||||
| 
 | ||||
|         return sock, (hostname, port, resource) | ||||
|     except: | ||||
|         if sock: | ||||
|             sock.close() | ||||
|         raise | ||||
| 
 | ||||
| 
 | ||||
| def _get_addrinfo_list(hostname, port, is_secure, proxy): | ||||
|     phost, pport, pauth = get_proxy_info( | ||||
|         hostname, is_secure, proxy.host, proxy.port, proxy.auth, proxy.no_proxy) | ||||
|     try: | ||||
|         if not phost: | ||||
|             addrinfo_list = [ai for ai in socket.getaddrinfo( | ||||
|                 hostname, port, 0, 0, socket.SOL_TCP) | ||||
|                 if (ai[0] == socket.AF_INET6 and socket.has_ipv6) or ai[0] != socket.AF_INET6 | ||||
|             ] | ||||
|             return addrinfo_list, False, None | ||||
|         else: | ||||
|             pport = pport and pport or 80 | ||||
|             # when running on windows 10, the getaddrinfo used above | ||||
|             # returns a socktype 0. This generates an error exception: | ||||
|             #_on_error: exception Socket type must be stream or datagram, not 0 | ||||
|             # Force the socket type to SOCK_STREAM | ||||
|             addrinfo_list = socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP) | ||||
|             return addrinfo_list, True, pauth | ||||
|     except socket.gaierror as e: | ||||
|         raise WebSocketAddressException(e) | ||||
| 
 | ||||
| 
 | ||||
| def _open_socket(addrinfo_list, sockopt, timeout): | ||||
|     err = None | ||||
|     for addrinfo in addrinfo_list: | ||||
|         family, socktype, proto = addrinfo[:3] | ||||
|         sock = socket.socket(family, socktype, proto) | ||||
|         sock.settimeout(timeout) | ||||
|         for opts in DEFAULT_SOCKET_OPTION: | ||||
|             sock.setsockopt(*opts) | ||||
|         for opts in sockopt: | ||||
|             sock.setsockopt(*opts) | ||||
| 
 | ||||
|         address = addrinfo[4] | ||||
|         err = None | ||||
|         while not err: | ||||
|             try: | ||||
|                 sock.connect(address) | ||||
|             except ProxyConnectionError as error: | ||||
|                 err = WebSocketProxyException(str(error)) | ||||
|                 err.remote_ip = str(address[0]) | ||||
|                 continue | ||||
|             except socket.error as error: | ||||
|                 error.remote_ip = str(address[0]) | ||||
|                 try: | ||||
|                     eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED) | ||||
|                 except: | ||||
|                     eConnRefused = (errno.ECONNREFUSED, ) | ||||
|                 if error.errno == errno.EINTR: | ||||
|                     continue | ||||
|                 elif error.errno in eConnRefused: | ||||
|                     err = error | ||||
|                     continue | ||||
|                 else: | ||||
|                     raise error | ||||
|             else: | ||||
|                 break | ||||
|         else: | ||||
|             continue | ||||
|         break | ||||
|     else: | ||||
|         if err: | ||||
|             raise err | ||||
| 
 | ||||
|     return sock | ||||
| 
 | ||||
| 
 | ||||
| def _can_use_sni(): | ||||
|     return six.PY2 and sys.version_info >= (2, 7, 9) or sys.version_info >= (3, 2) | ||||
| 
 | ||||
| 
 | ||||
| def _wrap_sni_socket(sock, sslopt, hostname, check_hostname): | ||||
|     context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_SSLv23)) | ||||
| 
 | ||||
|     if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE: | ||||
|         cafile = sslopt.get('ca_certs', None) | ||||
|         capath = sslopt.get('ca_cert_path', None) | ||||
|         if cafile or capath: | ||||
|             context.load_verify_locations(cafile=cafile, capath=capath) | ||||
|         elif hasattr(context, 'load_default_certs'): | ||||
|             context.load_default_certs(ssl.Purpose.SERVER_AUTH) | ||||
|     if sslopt.get('certfile', None): | ||||
|         context.load_cert_chain( | ||||
|             sslopt['certfile'], | ||||
|             sslopt.get('keyfile', None), | ||||
|             sslopt.get('password', None), | ||||
|         ) | ||||
|     # see | ||||
|     # https://github.com/liris/websocket-client/commit/b96a2e8fa765753e82eea531adb19716b52ca3ca#commitcomment-10803153 | ||||
|     context.verify_mode = sslopt['cert_reqs'] | ||||
|     if HAVE_CONTEXT_CHECK_HOSTNAME: | ||||
|         context.check_hostname = check_hostname | ||||
|     if 'ciphers' in sslopt: | ||||
|         context.set_ciphers(sslopt['ciphers']) | ||||
|     if 'cert_chain' in sslopt: | ||||
|         certfile, keyfile, password = sslopt['cert_chain'] | ||||
|         context.load_cert_chain(certfile, keyfile, password) | ||||
|     if 'ecdh_curve' in sslopt: | ||||
|         context.set_ecdh_curve(sslopt['ecdh_curve']) | ||||
| 
 | ||||
|     return context.wrap_socket( | ||||
|         sock, | ||||
|         do_handshake_on_connect=sslopt.get('do_handshake_on_connect', True), | ||||
|         suppress_ragged_eofs=sslopt.get('suppress_ragged_eofs', True), | ||||
|         server_hostname=hostname, | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def _ssl_socket(sock, user_sslopt, hostname): | ||||
|     sslopt = dict(cert_reqs=ssl.CERT_REQUIRED) | ||||
|     sslopt.update(user_sslopt) | ||||
| 
 | ||||
|     certPath = os.environ.get('WEBSOCKET_CLIENT_CA_BUNDLE') | ||||
|     if certPath and os.path.isfile(certPath) \ | ||||
|             and user_sslopt.get('ca_certs', None) is None \ | ||||
|             and user_sslopt.get('ca_cert', None) is None: | ||||
|         sslopt['ca_certs'] = certPath | ||||
|     elif certPath and os.path.isdir(certPath) \ | ||||
|             and user_sslopt.get('ca_cert_path', None) is None: | ||||
|         sslopt['ca_cert_path'] = certPath | ||||
| 
 | ||||
|     check_hostname = sslopt["cert_reqs"] != ssl.CERT_NONE and sslopt.pop( | ||||
|         'check_hostname', True) | ||||
| 
 | ||||
|     if _can_use_sni(): | ||||
|         sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname) | ||||
|     else: | ||||
|         sslopt.pop('check_hostname', True) | ||||
|         sock = ssl.wrap_socket(sock, **sslopt) | ||||
| 
 | ||||
|     if not HAVE_CONTEXT_CHECK_HOSTNAME and check_hostname: | ||||
|         match_hostname(sock.getpeercert(), hostname) | ||||
| 
 | ||||
|     return sock | ||||
| 
 | ||||
| 
 | ||||
| def _tunnel(sock, host, port, auth): | ||||
|     debug("Connecting proxy...") | ||||
|     connect_header = "CONNECT %s:%d HTTP/1.0\r\n" % (host, port) | ||||
|     # TODO: support digest auth. | ||||
|     if auth and auth[0]: | ||||
|         auth_str = auth[0] | ||||
|         if auth[1]: | ||||
|             auth_str += ":" + auth[1] | ||||
|         encoded_str = base64encode(auth_str.encode()).strip().decode() | ||||
|         connect_header += "Proxy-Authorization: Basic %s\r\n" % encoded_str | ||||
|     connect_header += "\r\n" | ||||
|     dump("request header", connect_header) | ||||
| 
 | ||||
|     send(sock, connect_header) | ||||
| 
 | ||||
|     try: | ||||
|         status, resp_headers, status_message = read_headers(sock) | ||||
|     except Exception as e: | ||||
|         raise WebSocketProxyException(str(e)) | ||||
| 
 | ||||
|     if status != 200: | ||||
|         raise WebSocketProxyException( | ||||
|             "failed CONNECT via proxy status: %r" % status) | ||||
| 
 | ||||
|     return sock | ||||
| 
 | ||||
| 
 | ||||
| def read_headers(sock): | ||||
|     status = None | ||||
|     status_message = None | ||||
|     headers = {} | ||||
|     trace("--- response header ---") | ||||
| 
 | ||||
|     while True: | ||||
|         line = recv_line(sock) | ||||
|         line = line.decode('utf-8').strip() | ||||
|         if not line: | ||||
|             break | ||||
|         trace(line) | ||||
|         if not status: | ||||
| 
 | ||||
|             status_info = line.split(" ", 2) | ||||
|             status = int(status_info[1]) | ||||
|             if len(status_info) > 2: | ||||
|                 status_message = status_info[2] | ||||
|         else: | ||||
|             kv = line.split(":", 1) | ||||
|             if len(kv) == 2: | ||||
|                 key, value = kv | ||||
|                 headers[key.lower()] = value.strip() | ||||
|             else: | ||||
|                 raise WebSocketException("Invalid header") | ||||
| 
 | ||||
|     trace("-----------------------") | ||||
| 
 | ||||
|     return status, headers, status_message | ||||
| @ -0,0 +1,82 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA  02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| import logging | ||||
| 
 | ||||
| _logger = logging.getLogger('websocket') | ||||
| try: | ||||
|     from logging import NullHandler | ||||
| except ImportError: | ||||
|     class NullHandler(logging.Handler): | ||||
|         def emit(self, record): | ||||
|             pass | ||||
| 
 | ||||
| _logger.addHandler(NullHandler()) | ||||
| 
 | ||||
| _traceEnabled = False | ||||
| 
 | ||||
| __all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace", | ||||
|            "isEnabledForError", "isEnabledForDebug"] | ||||
| 
 | ||||
| 
 | ||||
| def enableTrace(traceable, handler = logging.StreamHandler()): | ||||
|     """ | ||||
|     turn on/off the traceability. | ||||
| 
 | ||||
|     traceable: boolean value. if set True, traceability is enabled. | ||||
|     """ | ||||
|     global _traceEnabled | ||||
|     _traceEnabled = traceable | ||||
|     if traceable: | ||||
|         _logger.addHandler(handler) | ||||
|         _logger.setLevel(logging.DEBUG) | ||||
| 
 | ||||
| 
 | ||||
| def dump(title, message): | ||||
|     if _traceEnabled: | ||||
|         _logger.debug("--- " + title + " ---") | ||||
|         _logger.debug(message) | ||||
|         _logger.debug("-----------------------") | ||||
| 
 | ||||
| 
 | ||||
| def error(msg): | ||||
|     _logger.error(msg) | ||||
| 
 | ||||
| 
 | ||||
| def warning(msg): | ||||
|     _logger.warning(msg) | ||||
| 
 | ||||
| 
 | ||||
| def debug(msg): | ||||
|     _logger.debug(msg) | ||||
| 
 | ||||
| 
 | ||||
| def trace(msg): | ||||
|     if _traceEnabled: | ||||
|         _logger.debug(msg) | ||||
| 
 | ||||
| 
 | ||||
| def isEnabledForError(): | ||||
|     return _logger.isEnabledFor(logging.ERROR) | ||||
| 
 | ||||
| 
 | ||||
| def isEnabledForDebug(): | ||||
|     return _logger.isEnabledFor(logging.DEBUG) | ||||
| @ -0,0 +1,160 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA 02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| import errno | ||||
| import select | ||||
| import socket | ||||
| 
 | ||||
| import six | ||||
| import sys | ||||
| 
 | ||||
| from ._exceptions import * | ||||
| from ._ssl_compat import * | ||||
| from ._utils import * | ||||
| 
 | ||||
| DEFAULT_SOCKET_OPTION = [(socket.SOL_TCP, socket.TCP_NODELAY, 1)] | ||||
| if hasattr(socket, "SO_KEEPALIVE"): | ||||
|     DEFAULT_SOCKET_OPTION.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)) | ||||
| if hasattr(socket, "TCP_KEEPIDLE"): | ||||
|     DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPIDLE, 30)) | ||||
| if hasattr(socket, "TCP_KEEPINTVL"): | ||||
|     DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPINTVL, 10)) | ||||
| if hasattr(socket, "TCP_KEEPCNT"): | ||||
|     DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPCNT, 3)) | ||||
| 
 | ||||
| _default_timeout = None | ||||
| 
 | ||||
| __all__ = ["DEFAULT_SOCKET_OPTION", "sock_opt", "setdefaulttimeout", "getdefaulttimeout", | ||||
|            "recv", "recv_line", "send"] | ||||
| 
 | ||||
| 
 | ||||
| class sock_opt(object): | ||||
| 
 | ||||
|     def __init__(self, sockopt, sslopt): | ||||
|         if sockopt is None: | ||||
|             sockopt = [] | ||||
|         if sslopt is None: | ||||
|             sslopt = {} | ||||
|         self.sockopt = sockopt | ||||
|         self.sslopt = sslopt | ||||
|         self.timeout = None | ||||
| 
 | ||||
| 
 | ||||
| def setdefaulttimeout(timeout): | ||||
|     """ | ||||
|     Set the global timeout setting to connect. | ||||
| 
 | ||||
|     timeout: default socket timeout time. This value is second. | ||||
|     """ | ||||
|     global _default_timeout | ||||
|     _default_timeout = timeout | ||||
| 
 | ||||
| 
 | ||||
| def getdefaulttimeout(): | ||||
|     """ | ||||
|     Return the global timeout setting(second) to connect. | ||||
|     """ | ||||
|     return _default_timeout | ||||
| 
 | ||||
| 
 | ||||
| def recv(sock, bufsize): | ||||
|     if not sock: | ||||
|         raise WebSocketConnectionClosedException("socket is already closed.") | ||||
| 
 | ||||
|     def _recv(): | ||||
|         try: | ||||
|             return sock.recv(bufsize) | ||||
|         except SSLWantReadError: | ||||
|             pass | ||||
|         except socket.error as exc: | ||||
|             error_code = extract_error_code(exc) | ||||
|             if error_code is None: | ||||
|                 raise | ||||
|             if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK: | ||||
|                 raise | ||||
| 
 | ||||
|         r, w, e = select.select((sock, ), (), (), sock.gettimeout()) | ||||
|         if r: | ||||
|             return sock.recv(bufsize) | ||||
| 
 | ||||
|     try: | ||||
|         bytes_ = _recv() | ||||
|     except socket.timeout as e: | ||||
|         message = extract_err_message(e) | ||||
|         raise WebSocketTimeoutException(message) | ||||
|     except SSLError as e: | ||||
|         message = extract_err_message(e) | ||||
|         if isinstance(message, str) and 'timed out' in message: | ||||
|             raise WebSocketTimeoutException(message) | ||||
|         else: | ||||
|             raise | ||||
| 
 | ||||
|     if not bytes_: | ||||
|         raise WebSocketConnectionClosedException( | ||||
|             "Connection is already closed.") | ||||
| 
 | ||||
|     return bytes_ | ||||
| 
 | ||||
| 
 | ||||
| def recv_line(sock): | ||||
|     line = [] | ||||
|     while True: | ||||
|         c = recv(sock, 1) | ||||
|         line.append(c) | ||||
|         if c == six.b("\n"): | ||||
|             break | ||||
|     return six.b("").join(line) | ||||
| 
 | ||||
| 
 | ||||
| def send(sock, data): | ||||
|     if isinstance(data, six.text_type): | ||||
|         data = data.encode('utf-8') | ||||
| 
 | ||||
|     if not sock: | ||||
|         raise WebSocketConnectionClosedException("socket is already closed.") | ||||
| 
 | ||||
|     def _send(): | ||||
|         try: | ||||
|             return sock.send(data) | ||||
|         except SSLWantWriteError: | ||||
|             pass | ||||
|         except socket.error as exc: | ||||
|             error_code = extract_error_code(exc) | ||||
|             if error_code is None: | ||||
|                 raise | ||||
|             if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK: | ||||
|                 raise | ||||
| 
 | ||||
|         r, w, e = select.select((), (sock, ), (), sock.gettimeout()) | ||||
|         if w: | ||||
|             return sock.send(data) | ||||
| 
 | ||||
|     try: | ||||
|         return _send() | ||||
|     except socket.timeout as e: | ||||
|         message = extract_err_message(e) | ||||
|         raise WebSocketTimeoutException(message) | ||||
|     except Exception as e: | ||||
|         message = extract_err_message(e) | ||||
|         if isinstance(message, str) and "timed out" in message: | ||||
|             raise WebSocketTimeoutException(message) | ||||
|         else: | ||||
|             raise | ||||
| @ -0,0 +1,52 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA 02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| __all__ = ["HAVE_SSL", "ssl", "SSLError", "SSLWantReadError", "SSLWantWriteError"] | ||||
| 
 | ||||
| try: | ||||
|     import ssl | ||||
|     from ssl import SSLError | ||||
|     from ssl import SSLWantReadError | ||||
|     from ssl import SSLWantWriteError | ||||
|     if hasattr(ssl, 'SSLContext') and hasattr(ssl.SSLContext, 'check_hostname'): | ||||
|         HAVE_CONTEXT_CHECK_HOSTNAME = True | ||||
|     else: | ||||
|         HAVE_CONTEXT_CHECK_HOSTNAME = False | ||||
|         if hasattr(ssl, "match_hostname"): | ||||
|             from ssl import match_hostname | ||||
|         else: | ||||
|             from backports.ssl_match_hostname import match_hostname | ||||
|         __all__.append("match_hostname") | ||||
|     __all__.append("HAVE_CONTEXT_CHECK_HOSTNAME") | ||||
| 
 | ||||
|     HAVE_SSL = True | ||||
| except ImportError: | ||||
|     # dummy class of SSLError for ssl none-support environment. | ||||
|     class SSLError(Exception): | ||||
|         pass | ||||
| 
 | ||||
|     class SSLWantReadError(Exception): | ||||
|         pass | ||||
| 
 | ||||
|     class SSLWantWriteError(Exception): | ||||
|         pass | ||||
| 
 | ||||
|     HAVE_SSL = False | ||||
| @ -0,0 +1,163 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA  02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| 
 | ||||
| import os | ||||
| import socket | ||||
| import struct | ||||
| 
 | ||||
| from six.moves.urllib.parse import urlparse | ||||
| 
 | ||||
| 
 | ||||
| __all__ = ["parse_url", "get_proxy_info"] | ||||
| 
 | ||||
| 
 | ||||
| def parse_url(url): | ||||
|     """ | ||||
|     parse url and the result is tuple of | ||||
|     (hostname, port, resource path and the flag of secure mode) | ||||
| 
 | ||||
|     url: url string. | ||||
|     """ | ||||
|     if ":" not in url: | ||||
|         raise ValueError("url is invalid") | ||||
| 
 | ||||
|     scheme, url = url.split(":", 1) | ||||
| 
 | ||||
|     parsed = urlparse(url, scheme="ws") | ||||
|     if parsed.hostname: | ||||
|         hostname = parsed.hostname | ||||
|     else: | ||||
|         raise ValueError("hostname is invalid") | ||||
|     port = 0 | ||||
|     if parsed.port: | ||||
|         port = parsed.port | ||||
| 
 | ||||
|     is_secure = False | ||||
|     if scheme == "ws": | ||||
|         if not port: | ||||
|             port = 80 | ||||
|     elif scheme == "wss": | ||||
|         is_secure = True | ||||
|         if not port: | ||||
|             port = 443 | ||||
|     else: | ||||
|         raise ValueError("scheme %s is invalid" % scheme) | ||||
| 
 | ||||
|     if parsed.path: | ||||
|         resource = parsed.path | ||||
|     else: | ||||
|         resource = "/" | ||||
| 
 | ||||
|     if parsed.query: | ||||
|         resource += "?" + parsed.query | ||||
| 
 | ||||
|     return hostname, port, resource, is_secure | ||||
| 
 | ||||
| 
 | ||||
| DEFAULT_NO_PROXY_HOST = ["localhost", "127.0.0.1"] | ||||
| 
 | ||||
| 
 | ||||
| def _is_ip_address(addr): | ||||
|     try: | ||||
|         socket.inet_aton(addr) | ||||
|     except socket.error: | ||||
|         return False | ||||
|     else: | ||||
|         return True | ||||
| 
 | ||||
| 
 | ||||
| def _is_subnet_address(hostname): | ||||
|     try: | ||||
|         addr, netmask = hostname.split("/") | ||||
|         return _is_ip_address(addr) and 0 <= int(netmask) < 32 | ||||
|     except ValueError: | ||||
|         return False | ||||
| 
 | ||||
| 
 | ||||
| def _is_address_in_network(ip, net): | ||||
|     ipaddr = struct.unpack('I', socket.inet_aton(ip))[0] | ||||
|     netaddr, bits = net.split('/') | ||||
|     netmask = struct.unpack('I', socket.inet_aton(netaddr))[0] & ((2 << int(bits) - 1) - 1) | ||||
|     return ipaddr & netmask == netmask | ||||
| 
 | ||||
| 
 | ||||
| def _is_no_proxy_host(hostname, no_proxy): | ||||
|     if not no_proxy: | ||||
|         v = os.environ.get("no_proxy", "").replace(" ", "") | ||||
|         no_proxy = v.split(",") | ||||
|     if not no_proxy: | ||||
|         no_proxy = DEFAULT_NO_PROXY_HOST | ||||
| 
 | ||||
|     if hostname in no_proxy: | ||||
|         return True | ||||
|     elif _is_ip_address(hostname): | ||||
|         return any([_is_address_in_network(hostname, subnet) for subnet in no_proxy if _is_subnet_address(subnet)]) | ||||
| 
 | ||||
|     return False | ||||
| 
 | ||||
| 
 | ||||
| def get_proxy_info( | ||||
|         hostname, is_secure, proxy_host=None, proxy_port=0, proxy_auth=None, | ||||
|         no_proxy=None, proxy_type='http'): | ||||
|     """ | ||||
|     try to retrieve proxy host and port from environment | ||||
|     if not provided in options. | ||||
|     result is (proxy_host, proxy_port, proxy_auth). | ||||
|     proxy_auth is tuple of username and password | ||||
|      of proxy authentication information. | ||||
| 
 | ||||
|     hostname: websocket server name. | ||||
| 
 | ||||
|     is_secure:  is the connection secure? (wss) | ||||
|                 looks for "https_proxy" in env | ||||
|                 before falling back to "http_proxy" | ||||
| 
 | ||||
|     options:    "http_proxy_host" - http proxy host name. | ||||
|                 "http_proxy_port" - http proxy port. | ||||
|                 "http_no_proxy"   - host names, which doesn't use proxy. | ||||
|                 "http_proxy_auth" - http proxy auth information. | ||||
|                                     tuple of username and password. | ||||
|                                     default is None | ||||
|                 "proxy_type"      - if set to "socks5" PySocks wrapper | ||||
|                                     will be used in place of a http proxy. | ||||
|                                     default is "http" | ||||
|     """ | ||||
|     if _is_no_proxy_host(hostname, no_proxy): | ||||
|         return None, 0, None | ||||
| 
 | ||||
|     if proxy_host: | ||||
|         port = proxy_port | ||||
|         auth = proxy_auth | ||||
|         return proxy_host, port, auth | ||||
| 
 | ||||
|     env_keys = ["http_proxy"] | ||||
|     if is_secure: | ||||
|         env_keys.insert(0, "https_proxy") | ||||
| 
 | ||||
|     for key in env_keys: | ||||
|         value = os.environ.get(key, None) | ||||
|         if value: | ||||
|             proxy = urlparse(value) | ||||
|             auth = (proxy.username, proxy.password) if proxy.username else None | ||||
|             return proxy.hostname, proxy.port, auth | ||||
| 
 | ||||
|     return None, 0, None | ||||
| @ -0,0 +1,110 @@ | ||||
| """ | ||||
| websocket - WebSocket client library for Python | ||||
| 
 | ||||
| Copyright (C) 2010 Hiroki Ohtani(liris) | ||||
| 
 | ||||
|     This library is free software; you can redistribute it and/or | ||||
|     modify it under the terms of the GNU Lesser General Public | ||||
|     License as published by the Free Software Foundation; either | ||||
|     version 2.1 of the License, or (at your option) any later version. | ||||
| 
 | ||||
|     This library is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|     Lesser General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU Lesser General Public | ||||
|     License along with this library; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||||
|     Boston, MA 02110-1335  USA | ||||
| 
 | ||||
| """ | ||||
| import six | ||||
| 
 | ||||
| __all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code"] | ||||
| 
 | ||||
| 
 | ||||
| class NoLock(object): | ||||
| 
 | ||||
|     def __enter__(self): | ||||
|         pass | ||||
| 
 | ||||
|     def __exit__(self, exc_type, exc_value, traceback): | ||||
|         pass | ||||
| 
 | ||||
| try: | ||||
|     # If wsaccel is available we use compiled routines to validate UTF-8 | ||||
|     # strings. | ||||
|     from wsaccel.utf8validator import Utf8Validator | ||||
| 
 | ||||
|     def _validate_utf8(utfbytes): | ||||
|         return Utf8Validator().validate(utfbytes)[0] | ||||
| 
 | ||||
| except ImportError: | ||||
|     # UTF-8 validator | ||||
|     # python implementation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ | ||||
| 
 | ||||
|     _UTF8_ACCEPT = 0 | ||||
|     _UTF8_REJECT = 12 | ||||
| 
 | ||||
|     _UTF8D = [ | ||||
|         # The first part of the table maps bytes to character classes that | ||||
|         # to reduce the size of the transition table and create bitmasks. | ||||
|         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, | ||||
|         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, | ||||
|         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, | ||||
|         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, | ||||
|         1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, | ||||
|         7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, | ||||
|         8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, | ||||
|         10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, | ||||
| 
 | ||||
|         # The second part is a transition table that maps a combination | ||||
|         # of a state of the automaton and a character class to a state. | ||||
|         0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, | ||||
|         12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, | ||||
|         12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, | ||||
|         12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, | ||||
|         12,36,12,12,12,12,12,12,12,12,12,12, ] | ||||
| 
 | ||||
|     def _decode(state, codep, ch): | ||||
|         tp = _UTF8D[ch] | ||||
| 
 | ||||
|         codep = (ch & 0x3f) | (codep << 6) if ( | ||||
|             state != _UTF8_ACCEPT) else (0xff >> tp) & ch | ||||
|         state = _UTF8D[256 + state + tp] | ||||
| 
 | ||||
|         return state, codep | ||||
| 
 | ||||
|     def _validate_utf8(utfbytes): | ||||
|         state = _UTF8_ACCEPT | ||||
|         codep = 0 | ||||
|         for i in utfbytes: | ||||
|             if six.PY2: | ||||
|                 i = ord(i) | ||||
|             state, codep = _decode(state, codep, i) | ||||
|             if state == _UTF8_REJECT: | ||||
|                 return False | ||||
| 
 | ||||
|         return True | ||||
| 
 | ||||
| 
 | ||||
| def validate_utf8(utfbytes): | ||||
|     """ | ||||
|     validate utf8 byte string. | ||||
|     utfbytes: utf byte string to check. | ||||
|     return value: if valid utf8 string, return true. Otherwise, return false. | ||||
|     """ | ||||
|     return _validate_utf8(utfbytes) | ||||
| 
 | ||||
| 
 | ||||
| def extract_err_message(exception): | ||||
|     if exception.args: | ||||
|         return exception.args[0] | ||||
|     else: | ||||
|         return None | ||||
| 
 | ||||
| 
 | ||||
| def extract_error_code(exception): | ||||
|     if exception.args and len(exception.args) > 1: | ||||
|         return exception.args[0] if isinstance(exception.args[0], int) else None | ||||
| @ -0,0 +1,6 @@ | ||||
| HTTP/1.1 101 WebSocket Protocol Handshake | ||||
| Connection: Upgrade  | ||||
| Upgrade: WebSocket | ||||
| Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0= | ||||
| some_header: something | ||||
| 
 | ||||
| @ -0,0 +1,6 @@ | ||||
| HTTP/1.1 101 WebSocket Protocol Handshake | ||||
| Connection: Upgrade | ||||
| Upgrade WebSocket | ||||
| Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0= | ||||
| some_header: something | ||||
| 
 | ||||
| @ -0,0 +1,98 @@ | ||||
| import unittest | ||||
| 
 | ||||
| from websocket._cookiejar import SimpleCookieJar | ||||
| 
 | ||||
| try: | ||||
|     import Cookie | ||||
| except: | ||||
|     import http.cookies as Cookie | ||||
| 
 | ||||
| 
 | ||||
| class CookieJarTest(unittest.TestCase): | ||||
|     def testAdd(self): | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.add("") | ||||
|         self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.add("a=b") | ||||
|         self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.add("a=b; domain=.abc") | ||||
|         self.assertTrue(".abc" in cookie_jar.jar) | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.add("a=b; domain=abc") | ||||
|         self.assertTrue(".abc" in cookie_jar.jar) | ||||
|         self.assertTrue("abc" not in cookie_jar.jar) | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.add("a=b; c=d; domain=abc") | ||||
|         self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.add("a=b; c=d; domain=abc") | ||||
|         cookie_jar.add("e=f; domain=abc") | ||||
|         self.assertEquals(cookie_jar.get("abc"), "a=b; c=d; e=f") | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.add("a=b; c=d; domain=abc") | ||||
|         cookie_jar.add("e=f; domain=.abc") | ||||
|         self.assertEquals(cookie_jar.get("abc"), "a=b; c=d; e=f") | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.add("a=b; c=d; domain=abc") | ||||
|         cookie_jar.add("e=f; domain=xyz") | ||||
|         self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") | ||||
|         self.assertEquals(cookie_jar.get("xyz"), "e=f") | ||||
|         self.assertEquals(cookie_jar.get("something"), "") | ||||
| 
 | ||||
|     def testSet(self): | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.set("a=b") | ||||
|         self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.set("a=b; domain=.abc") | ||||
|         self.assertTrue(".abc" in cookie_jar.jar) | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.set("a=b; domain=abc") | ||||
|         self.assertTrue(".abc" in cookie_jar.jar) | ||||
|         self.assertTrue("abc" not in cookie_jar.jar) | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.set("a=b; c=d; domain=abc") | ||||
|         self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.set("a=b; c=d; domain=abc") | ||||
|         cookie_jar.set("e=f; domain=abc") | ||||
|         self.assertEquals(cookie_jar.get("abc"), "e=f") | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.set("a=b; c=d; domain=abc") | ||||
|         cookie_jar.set("e=f; domain=.abc") | ||||
|         self.assertEquals(cookie_jar.get("abc"), "e=f") | ||||
| 
 | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.set("a=b; c=d; domain=abc") | ||||
|         cookie_jar.set("e=f; domain=xyz") | ||||
|         self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") | ||||
|         self.assertEquals(cookie_jar.get("xyz"), "e=f") | ||||
|         self.assertEquals(cookie_jar.get("something"), "") | ||||
| 
 | ||||
|     def testGet(self): | ||||
|         cookie_jar = SimpleCookieJar() | ||||
|         cookie_jar.set("a=b; c=d; domain=abc.com") | ||||
|         self.assertEquals(cookie_jar.get("abc.com"), "a=b; c=d") | ||||
|         self.assertEquals(cookie_jar.get("x.abc.com"), "a=b; c=d") | ||||
|         self.assertEquals(cookie_jar.get("abc.com.es"), "") | ||||
|         self.assertEquals(cookie_jar.get("xabc.com"), "") | ||||
| 
 | ||||
|         cookie_jar.set("a=b; c=d; domain=.abc.com") | ||||
|         self.assertEquals(cookie_jar.get("abc.com"), "a=b; c=d") | ||||
|         self.assertEquals(cookie_jar.get("x.abc.com"), "a=b; c=d") | ||||
|         self.assertEquals(cookie_jar.get("abc.com.es"), "") | ||||
|         self.assertEquals(cookie_jar.get("xabc.com"), "") | ||||
| @ -0,0 +1,662 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| 
 | ||||
| import sys | ||||
| sys.path[0:0] = [""] | ||||
| 
 | ||||
| import os | ||||
| import os.path | ||||
| import socket | ||||
| 
 | ||||
| import six | ||||
| 
 | ||||
| # websocket-client | ||||
| import websocket as ws | ||||
| from websocket._handshake import _create_sec_websocket_key, \ | ||||
|     _validate as _validate_header | ||||
| from websocket._http import read_headers | ||||
| from websocket._url import get_proxy_info, parse_url | ||||
| from websocket._utils import validate_utf8 | ||||
| 
 | ||||
| if six.PY3: | ||||
|     from base64 import decodebytes as base64decode | ||||
| else: | ||||
|     from base64 import decodestring as base64decode | ||||
| 
 | ||||
| if sys.version_info[0] == 2 and sys.version_info[1] < 7: | ||||
|     import unittest2 as unittest | ||||
| else: | ||||
|     import unittest | ||||
| 
 | ||||
| try: | ||||
|     from ssl import SSLError | ||||
| except ImportError: | ||||
|     # dummy class of SSLError for ssl none-support environment. | ||||
|     class SSLError(Exception): | ||||
|         pass | ||||
| 
 | ||||
| # Skip test to access the internet. | ||||
| TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' | ||||
| 
 | ||||
| # Skip Secure WebSocket test. | ||||
| TEST_SECURE_WS = True | ||||
| TRACEABLE = True | ||||
| 
 | ||||
| 
 | ||||
| def create_mask_key(_): | ||||
|     return "abcd" | ||||
| 
 | ||||
| 
 | ||||
| class SockMock(object): | ||||
|     def __init__(self): | ||||
|         self.data = [] | ||||
|         self.sent = [] | ||||
| 
 | ||||
|     def add_packet(self, data): | ||||
|         self.data.append(data) | ||||
| 
 | ||||
|     def recv(self, bufsize): | ||||
|         if self.data: | ||||
|             e = self.data.pop(0) | ||||
|             if isinstance(e, Exception): | ||||
|                 raise e | ||||
|             if len(e) > bufsize: | ||||
|                 self.data.insert(0, e[bufsize:]) | ||||
|             return e[:bufsize] | ||||
| 
 | ||||
|     def send(self, data): | ||||
|         self.sent.append(data) | ||||
|         return len(data) | ||||
| 
 | ||||
|     def close(self): | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
| class HeaderSockMock(SockMock): | ||||
| 
 | ||||
|     def __init__(self, fname): | ||||
|         SockMock.__init__(self) | ||||
|         path = os.path.join(os.path.dirname(__file__), fname) | ||||
|         with open(path, "rb") as f: | ||||
|             self.add_packet(f.read()) | ||||
| 
 | ||||
| 
 | ||||
| class WebSocketTest(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         ws.enableTrace(TRACEABLE) | ||||
| 
 | ||||
|     def tearDown(self): | ||||
|         pass | ||||
| 
 | ||||
|     def testDefaultTimeout(self): | ||||
|         self.assertEqual(ws.getdefaulttimeout(), None) | ||||
|         ws.setdefaulttimeout(10) | ||||
|         self.assertEqual(ws.getdefaulttimeout(), 10) | ||||
|         ws.setdefaulttimeout(None) | ||||
| 
 | ||||
|     def testParseUrl(self): | ||||
|         p = parse_url("ws://www.example.com/r") | ||||
|         self.assertEqual(p[0], "www.example.com") | ||||
|         self.assertEqual(p[1], 80) | ||||
|         self.assertEqual(p[2], "/r") | ||||
|         self.assertEqual(p[3], False) | ||||
| 
 | ||||
|         p = parse_url("ws://www.example.com/r/") | ||||
|         self.assertEqual(p[0], "www.example.com") | ||||
|         self.assertEqual(p[1], 80) | ||||
|         self.assertEqual(p[2], "/r/") | ||||
|         self.assertEqual(p[3], False) | ||||
| 
 | ||||
|         p = parse_url("ws://www.example.com/") | ||||
|         self.assertEqual(p[0], "www.example.com") | ||||
|         self.assertEqual(p[1], 80) | ||||
|         self.assertEqual(p[2], "/") | ||||
|         self.assertEqual(p[3], False) | ||||
| 
 | ||||
|         p = parse_url("ws://www.example.com") | ||||
|         self.assertEqual(p[0], "www.example.com") | ||||
|         self.assertEqual(p[1], 80) | ||||
|         self.assertEqual(p[2], "/") | ||||
|         self.assertEqual(p[3], False) | ||||
| 
 | ||||
|         p = parse_url("ws://www.example.com:8080/r") | ||||
|         self.assertEqual(p[0], "www.example.com") | ||||
|         self.assertEqual(p[1], 8080) | ||||
|         self.assertEqual(p[2], "/r") | ||||
|         self.assertEqual(p[3], False) | ||||
| 
 | ||||
|         p = parse_url("ws://www.example.com:8080/") | ||||
|         self.assertEqual(p[0], "www.example.com") | ||||
|         self.assertEqual(p[1], 8080) | ||||
|         self.assertEqual(p[2], "/") | ||||
|         self.assertEqual(p[3], False) | ||||
| 
 | ||||
|         p = parse_url("ws://www.example.com:8080") | ||||
|         self.assertEqual(p[0], "www.example.com") | ||||
|         self.assertEqual(p[1], 8080) | ||||
|         self.assertEqual(p[2], "/") | ||||
|         self.assertEqual(p[3], False) | ||||
| 
 | ||||
|         p = parse_url("wss://www.example.com:8080/r") | ||||
|         self.assertEqual(p[0], "www.example.com") | ||||
|         self.assertEqual(p[1], 8080) | ||||
|         self.assertEqual(p[2], "/r") | ||||
|         self.assertEqual(p[3], True) | ||||
| 
 | ||||
|         p = parse_url("wss://www.example.com:8080/r?key=value") | ||||
|         self.assertEqual(p[0], "www.example.com") | ||||
|         self.assertEqual(p[1], 8080) | ||||
|         self.assertEqual(p[2], "/r?key=value") | ||||
|         self.assertEqual(p[3], True) | ||||
| 
 | ||||
|         self.assertRaises(ValueError, parse_url, "http://www.example.com/r") | ||||
| 
 | ||||
|         if sys.version_info[0] == 2 and sys.version_info[1] < 7: | ||||
|             return | ||||
| 
 | ||||
|         p = parse_url("ws://[2a03:4000:123:83::3]/r") | ||||
|         self.assertEqual(p[0], "2a03:4000:123:83::3") | ||||
|         self.assertEqual(p[1], 80) | ||||
|         self.assertEqual(p[2], "/r") | ||||
|         self.assertEqual(p[3], False) | ||||
| 
 | ||||
|         p = parse_url("ws://[2a03:4000:123:83::3]:8080/r") | ||||
|         self.assertEqual(p[0], "2a03:4000:123:83::3") | ||||
|         self.assertEqual(p[1], 8080) | ||||
|         self.assertEqual(p[2], "/r") | ||||
|         self.assertEqual(p[3], False) | ||||
| 
 | ||||
|         p = parse_url("wss://[2a03:4000:123:83::3]/r") | ||||
|         self.assertEqual(p[0], "2a03:4000:123:83::3") | ||||
|         self.assertEqual(p[1], 443) | ||||
|         self.assertEqual(p[2], "/r") | ||||
|         self.assertEqual(p[3], True) | ||||
| 
 | ||||
|         p = parse_url("wss://[2a03:4000:123:83::3]:8080/r") | ||||
|         self.assertEqual(p[0], "2a03:4000:123:83::3") | ||||
|         self.assertEqual(p[1], 8080) | ||||
|         self.assertEqual(p[2], "/r") | ||||
|         self.assertEqual(p[3], True) | ||||
| 
 | ||||
|     def testWSKey(self): | ||||
|         key = _create_sec_websocket_key() | ||||
|         self.assertTrue(key != 24) | ||||
|         self.assertTrue(six.u("¥n") not in key) | ||||
| 
 | ||||
|     def testWsUtils(self): | ||||
|         key = "c6b8hTg4EeGb2gQMztV1/g==" | ||||
|         required_header = { | ||||
|             "upgrade": "websocket", | ||||
|             "connection": "upgrade", | ||||
|             "sec-websocket-accept": "Kxep+hNu9n51529fGidYu7a3wO0=", | ||||
|             } | ||||
|         self.assertEqual(_validate_header(required_header, key, None), (True, None)) | ||||
| 
 | ||||
|         header = required_header.copy() | ||||
|         header["upgrade"] = "http" | ||||
|         self.assertEqual(_validate_header(header, key, None), (False, None)) | ||||
|         del header["upgrade"] | ||||
|         self.assertEqual(_validate_header(header, key, None), (False, None)) | ||||
| 
 | ||||
|         header = required_header.copy() | ||||
|         header["connection"] = "something" | ||||
|         self.assertEqual(_validate_header(header, key, None), (False, None)) | ||||
|         del header["connection"] | ||||
|         self.assertEqual(_validate_header(header, key, None), (False, None)) | ||||
| 
 | ||||
|         header = required_header.copy() | ||||
|         header["sec-websocket-accept"] = "something" | ||||
|         self.assertEqual(_validate_header(header, key, None), (False, None)) | ||||
|         del header["sec-websocket-accept"] | ||||
|         self.assertEqual(_validate_header(header, key, None), (False, None)) | ||||
| 
 | ||||
|         header = required_header.copy() | ||||
|         header["sec-websocket-protocol"] = "sub1" | ||||
|         self.assertEqual(_validate_header(header, key, ["sub1", "sub2"]), (True, "sub1")) | ||||
|         self.assertEqual(_validate_header(header, key, ["sub2", "sub3"]), (False, None)) | ||||
| 
 | ||||
|         header = required_header.copy() | ||||
|         header["sec-websocket-protocol"] = "sUb1" | ||||
|         self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (True, "sub1")) | ||||
| 
 | ||||
| 
 | ||||
|     def testReadHeader(self): | ||||
|         status, header, status_message = read_headers(HeaderSockMock("data/header01.txt")) | ||||
|         self.assertEqual(status, 101) | ||||
|         self.assertEqual(header["connection"], "Upgrade") | ||||
| 
 | ||||
|         HeaderSockMock("data/header02.txt") | ||||
|         self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt")) | ||||
| 
 | ||||
|     def testSend(self): | ||||
|         # TODO: add longer frame data | ||||
|         sock = ws.WebSocket() | ||||
|         sock.set_mask_key(create_mask_key) | ||||
|         s = sock.sock = HeaderSockMock("data/header01.txt") | ||||
|         sock.send("Hello") | ||||
|         self.assertEqual(s.sent[0], six.b("\x81\x85abcd)\x07\x0f\x08\x0e")) | ||||
| 
 | ||||
|         sock.send("こんにちは") | ||||
|         self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")) | ||||
| 
 | ||||
|         sock.send(u"こんにちは") | ||||
|         self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")) | ||||
| 
 | ||||
|         sock.send("x" * 127) | ||||
| 
 | ||||
|     def testRecv(self): | ||||
|         # TODO: add longer frame data | ||||
|         sock = ws.WebSocket() | ||||
|         s = sock.sock = SockMock() | ||||
|         something = six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc") | ||||
|         s.add_packet(something) | ||||
|         data = sock.recv() | ||||
|         self.assertEqual(data, "こんにちは") | ||||
| 
 | ||||
|         s.add_packet(six.b("\x81\x85abcd)\x07\x0f\x08\x0e")) | ||||
|         data = sock.recv() | ||||
|         self.assertEqual(data, "Hello") | ||||
| 
 | ||||
|     @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") | ||||
|     def testIter(self): | ||||
|         count = 2 | ||||
|         for _ in ws.create_connection('ws://stream.meetup.com/2/rsvps'): | ||||
|             count -= 1 | ||||
|             if count == 0: | ||||
|                 break | ||||
| 
 | ||||
|     @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") | ||||
|     def testNext(self): | ||||
|         sock = ws.create_connection('ws://stream.meetup.com/2/rsvps') | ||||
|         self.assertEqual(str, type(next(sock))) | ||||
| 
 | ||||
|     def testInternalRecvStrict(self): | ||||
|         sock = ws.WebSocket() | ||||
|         s = sock.sock = SockMock() | ||||
|         s.add_packet(six.b("foo")) | ||||
|         s.add_packet(socket.timeout()) | ||||
|         s.add_packet(six.b("bar")) | ||||
|         # s.add_packet(SSLError("The read operation timed out")) | ||||
|         s.add_packet(six.b("baz")) | ||||
|         with self.assertRaises(ws.WebSocketTimeoutException): | ||||
|             sock.frame_buffer.recv_strict(9) | ||||
|         # if six.PY2: | ||||
|         #     with self.assertRaises(ws.WebSocketTimeoutException): | ||||
|         #         data = sock._recv_strict(9) | ||||
|         # else: | ||||
|         #     with self.assertRaises(SSLError): | ||||
|         #         data = sock._recv_strict(9) | ||||
|         data = sock.frame_buffer.recv_strict(9) | ||||
|         self.assertEqual(data, six.b("foobarbaz")) | ||||
|         with self.assertRaises(ws.WebSocketConnectionClosedException): | ||||
|             sock.frame_buffer.recv_strict(1) | ||||
| 
 | ||||
|     def testRecvTimeout(self): | ||||
|         sock = ws.WebSocket() | ||||
|         s = sock.sock = SockMock() | ||||
|         s.add_packet(six.b("\x81")) | ||||
|         s.add_packet(socket.timeout()) | ||||
|         s.add_packet(six.b("\x8dabcd\x29\x07\x0f\x08\x0e")) | ||||
|         s.add_packet(socket.timeout()) | ||||
|         s.add_packet(six.b("\x4e\x43\x33\x0e\x10\x0f\x00\x40")) | ||||
|         with self.assertRaises(ws.WebSocketTimeoutException): | ||||
|             sock.recv() | ||||
|         with self.assertRaises(ws.WebSocketTimeoutException): | ||||
|             sock.recv() | ||||
|         data = sock.recv() | ||||
|         self.assertEqual(data, "Hello, World!") | ||||
|         with self.assertRaises(ws.WebSocketConnectionClosedException): | ||||
|             sock.recv() | ||||
| 
 | ||||
|     def testRecvWithSimpleFragmentation(self): | ||||
|         sock = ws.WebSocket() | ||||
|         s = sock.sock = SockMock() | ||||
|         # OPCODE=TEXT, FIN=0, MSG="Brevity is " | ||||
|         s.add_packet(six.b("\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) | ||||
|         # OPCODE=CONT, FIN=1, MSG="the soul of wit" | ||||
|         s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) | ||||
|         data = sock.recv() | ||||
|         self.assertEqual(data, "Brevity is the soul of wit") | ||||
|         with self.assertRaises(ws.WebSocketConnectionClosedException): | ||||
|             sock.recv() | ||||
| 
 | ||||
|     def testRecvWithFireEventOfFragmentation(self): | ||||
|         sock = ws.WebSocket(fire_cont_frame=True) | ||||
|         s = sock.sock = SockMock() | ||||
|         # OPCODE=TEXT, FIN=0, MSG="Brevity is " | ||||
|         s.add_packet(six.b("\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) | ||||
|         # OPCODE=CONT, FIN=0, MSG="Brevity is " | ||||
|         s.add_packet(six.b("\x00\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) | ||||
|         # OPCODE=CONT, FIN=1, MSG="the soul of wit" | ||||
|         s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) | ||||
| 
 | ||||
|         _, data = sock.recv_data() | ||||
|         self.assertEqual(data, six.b("Brevity is ")) | ||||
|         _, data = sock.recv_data() | ||||
|         self.assertEqual(data, six.b("Brevity is ")) | ||||
|         _, data = sock.recv_data() | ||||
|         self.assertEqual(data, six.b("the soul of wit")) | ||||
| 
 | ||||
|         # OPCODE=CONT, FIN=0, MSG="Brevity is " | ||||
|         s.add_packet(six.b("\x80\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) | ||||
| 
 | ||||
|         with self.assertRaises(ws.WebSocketException): | ||||
|             sock.recv_data() | ||||
| 
 | ||||
|         with self.assertRaises(ws.WebSocketConnectionClosedException): | ||||
|             sock.recv() | ||||
| 
 | ||||
|     def testClose(self): | ||||
|         sock = ws.WebSocket() | ||||
|         sock.sock = SockMock() | ||||
|         sock.connected = True | ||||
|         sock.close() | ||||
|         self.assertEqual(sock.connected, False) | ||||
| 
 | ||||
|         sock = ws.WebSocket() | ||||
|         s = sock.sock = SockMock() | ||||
|         sock.connected = True | ||||
|         s.add_packet(six.b('\x88\x80\x17\x98p\x84')) | ||||
|         sock.recv() | ||||
|         self.assertEqual(sock.connected, False) | ||||
| 
 | ||||
|     def testRecvContFragmentation(self): | ||||
|         sock = ws.WebSocket() | ||||
|         s = sock.sock = SockMock() | ||||
|         # OPCODE=CONT, FIN=1, MSG="the soul of wit" | ||||
|         s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) | ||||
|         self.assertRaises(ws.WebSocketException, sock.recv) | ||||
| 
 | ||||
|     def testRecvWithProlongedFragmentation(self): | ||||
|         sock = ws.WebSocket() | ||||
|         s = sock.sock = SockMock() | ||||
|         # OPCODE=TEXT, FIN=0, MSG="Once more unto the breach, " | ||||
|         s.add_packet(six.b("\x01\x9babcd.\x0c\x00\x01A\x0f\x0c\x16\x04B\x16\n\x15" | ||||
|                            "\rC\x10\t\x07C\x06\x13\x07\x02\x07\tNC")) | ||||
|         # OPCODE=CONT, FIN=0, MSG="dear friends, " | ||||
|         s.add_packet(six.b("\x00\x8eabcd\x05\x07\x02\x16A\x04\x11\r\x04\x0c\x07" | ||||
|                            "\x17MB")) | ||||
|         # OPCODE=CONT, FIN=1, MSG="once more" | ||||
|         s.add_packet(six.b("\x80\x89abcd\x0e\x0c\x00\x01A\x0f\x0c\x16\x04")) | ||||
|         data = sock.recv() | ||||
|         self.assertEqual( | ||||
|             data, | ||||
|             "Once more unto the breach, dear friends, once more") | ||||
|         with self.assertRaises(ws.WebSocketConnectionClosedException): | ||||
|             sock.recv() | ||||
| 
 | ||||
|     def testRecvWithFragmentationAndControlFrame(self): | ||||
|         sock = ws.WebSocket() | ||||
|         sock.set_mask_key(create_mask_key) | ||||
|         s = sock.sock = SockMock() | ||||
|         # OPCODE=TEXT, FIN=0, MSG="Too much " | ||||
|         s.add_packet(six.b("\x01\x89abcd5\r\x0cD\x0c\x17\x00\x0cA")) | ||||
|         # OPCODE=PING, FIN=1, MSG="Please PONG this" | ||||
|         s.add_packet(six.b("\x89\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17")) | ||||
|         # OPCODE=CONT, FIN=1, MSG="of a good thing" | ||||
|         s.add_packet(six.b("\x80\x8fabcd\x0e\x04C\x05A\x05\x0c\x0b\x05B\x17\x0c" | ||||
|                            "\x08\x0c\x04")) | ||||
|         data = sock.recv() | ||||
|         self.assertEqual(data, "Too much of a good thing") | ||||
|         with self.assertRaises(ws.WebSocketConnectionClosedException): | ||||
|             sock.recv() | ||||
|         self.assertEqual( | ||||
|             s.sent[0], | ||||
|             six.b("\x8a\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17")) | ||||
| 
 | ||||
|     @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") | ||||
|     def testWebSocket(self): | ||||
|         s = ws.create_connection("ws://echo.websocket.org/") | ||||
|         self.assertNotEqual(s, None) | ||||
|         s.send("Hello, World") | ||||
|         result = s.recv() | ||||
|         self.assertEqual(result, "Hello, World") | ||||
| 
 | ||||
|         s.send(u"こにゃにゃちは、世界") | ||||
|         result = s.recv() | ||||
|         self.assertEqual(result, "こにゃにゃちは、世界") | ||||
|         s.close() | ||||
| 
 | ||||
|     @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") | ||||
|     def testPingPong(self): | ||||
|         s = ws.create_connection("ws://echo.websocket.org/") | ||||
|         self.assertNotEqual(s, None) | ||||
|         s.ping("Hello") | ||||
|         s.pong("Hi") | ||||
|         s.close() | ||||
| 
 | ||||
|     @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") | ||||
|     @unittest.skipUnless(TEST_SECURE_WS, "wss://echo.websocket.org doesn't work well.") | ||||
|     def testSecureWebSocket(self): | ||||
|         if 1: | ||||
|             import ssl | ||||
|             s = ws.create_connection("wss://echo.websocket.org/") | ||||
|             self.assertNotEqual(s, None) | ||||
|             self.assertTrue(isinstance(s.sock, ssl.SSLSocket)) | ||||
|             s.send("Hello, World") | ||||
|             result = s.recv() | ||||
|             self.assertEqual(result, "Hello, World") | ||||
|             s.send(u"こにゃにゃちは、世界") | ||||
|             result = s.recv() | ||||
|             self.assertEqual(result, "こにゃにゃちは、世界") | ||||
|             s.close() | ||||
|         #except: | ||||
|         #    pass | ||||
| 
 | ||||
|     @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") | ||||
|     def testWebSocketWihtCustomHeader(self): | ||||
|         s = ws.create_connection("ws://echo.websocket.org/", | ||||
|                                  headers={"User-Agent": "PythonWebsocketClient"}) | ||||
|         self.assertNotEqual(s, None) | ||||
|         s.send("Hello, World") | ||||
|         result = s.recv() | ||||
|         self.assertEqual(result, "Hello, World") | ||||
|         s.close() | ||||
| 
 | ||||
|     @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") | ||||
|     def testAfterClose(self): | ||||
|         s = ws.create_connection("ws://echo.websocket.org/") | ||||
|         self.assertNotEqual(s, None) | ||||
|         s.close() | ||||
|         self.assertRaises(ws.WebSocketConnectionClosedException, s.send, "Hello") | ||||
|         self.assertRaises(ws.WebSocketConnectionClosedException, s.recv) | ||||
| 
 | ||||
|     def testNonce(self): | ||||
|         """ WebSocket key should be a random 16-byte nonce. | ||||
|         """ | ||||
|         key = _create_sec_websocket_key() | ||||
|         nonce = base64decode(key.encode("utf-8")) | ||||
|         self.assertEqual(16, len(nonce)) | ||||
| 
 | ||||
| 
 | ||||
| class WebSocketAppTest(unittest.TestCase): | ||||
| 
 | ||||
|     class NotSetYet(object): | ||||
|         """ A marker class for signalling that a value hasn't been set yet. | ||||
|         """ | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         ws.enableTrace(TRACEABLE) | ||||
| 
 | ||||
|         WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet() | ||||
|         WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() | ||||
|         WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() | ||||
| 
 | ||||
|     def tearDown(self): | ||||
|         WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet() | ||||
|         WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() | ||||
|         WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() | ||||
| 
 | ||||
|     @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") | ||||
|     def testKeepRunning(self): | ||||
|         """ A WebSocketApp should keep running as long as its self.keep_running | ||||
|         is not False (in the boolean context). | ||||
|         """ | ||||
| 
 | ||||
|         def on_open(self, *args, **kwargs): | ||||
|             """ Set the keep_running flag for later inspection and immediately | ||||
|             close the connection. | ||||
|             """ | ||||
|             WebSocketAppTest.keep_running_open = self.keep_running | ||||
| 
 | ||||
|             self.close() | ||||
| 
 | ||||
|         def on_close(self, *args, **kwargs): | ||||
|             """ Set the keep_running flag for the test to use. | ||||
|             """ | ||||
|             WebSocketAppTest.keep_running_close = self.keep_running | ||||
| 
 | ||||
|         app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, on_close=on_close) | ||||
|         app.run_forever() | ||||
| 
 | ||||
|         # if numpy is installed, this assertion fail | ||||
|         # self.assertFalse(isinstance(WebSocketAppTest.keep_running_open, | ||||
|         #                             WebSocketAppTest.NotSetYet)) | ||||
| 
 | ||||
|         # self.assertFalse(isinstance(WebSocketAppTest.keep_running_close, | ||||
|         #                             WebSocketAppTest.NotSetYet)) | ||||
| 
 | ||||
|         # self.assertEqual(True, WebSocketAppTest.keep_running_open) | ||||
|         # self.assertEqual(False, WebSocketAppTest.keep_running_close) | ||||
| 
 | ||||
|     @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") | ||||
|     def testSockMaskKey(self): | ||||
|         """ A WebSocketApp should forward the received mask_key function down | ||||
|         to the actual socket. | ||||
|         """ | ||||
| 
 | ||||
|         def my_mask_key_func(): | ||||
|             pass | ||||
| 
 | ||||
|         def on_open(self, *args, **kwargs): | ||||
|             """ Set the value so the test can use it later on and immediately | ||||
|             close the connection. | ||||
|             """ | ||||
|             WebSocketAppTest.get_mask_key_id = id(self.get_mask_key) | ||||
|             self.close() | ||||
| 
 | ||||
|         app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, get_mask_key=my_mask_key_func) | ||||
|         app.run_forever() | ||||
| 
 | ||||
|         # if numpu is installed, this assertion fail | ||||
|         # Note: We can't use 'is' for comparing the functions directly, need to use 'id'. | ||||
|         # self.assertEqual(WebSocketAppTest.get_mask_key_id, id(my_mask_key_func)) | ||||
| 
 | ||||
| 
 | ||||
| class SockOptTest(unittest.TestCase): | ||||
|     @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") | ||||
|     def testSockOpt(self): | ||||
|         sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),) | ||||
|         s = ws.create_connection("ws://echo.websocket.org", sockopt=sockopt) | ||||
|         self.assertNotEqual(s.sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY), 0) | ||||
|         s.close() | ||||
| 
 | ||||
| 
 | ||||
| class UtilsTest(unittest.TestCase): | ||||
|     def testUtf8Validator(self): | ||||
|         state = validate_utf8(six.b('\xf0\x90\x80\x80')) | ||||
|         self.assertEqual(state, True) | ||||
|         state = validate_utf8(six.b('\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\xed\xa0\x80edited')) | ||||
|         self.assertEqual(state, False) | ||||
|         state = validate_utf8(six.b('')) | ||||
|         self.assertEqual(state, True) | ||||
| 
 | ||||
| 
 | ||||
| class ProxyInfoTest(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         self.http_proxy = os.environ.get("http_proxy", None) | ||||
|         self.https_proxy = os.environ.get("https_proxy", None) | ||||
|         if "http_proxy" in os.environ: | ||||
|             del os.environ["http_proxy"] | ||||
|         if "https_proxy" in os.environ: | ||||
|             del os.environ["https_proxy"] | ||||
| 
 | ||||
|     def tearDown(self): | ||||
|         if self.http_proxy: | ||||
|             os.environ["http_proxy"] = self.http_proxy | ||||
|         elif "http_proxy" in os.environ: | ||||
|             del os.environ["http_proxy"] | ||||
| 
 | ||||
|         if self.https_proxy: | ||||
|             os.environ["https_proxy"] = self.https_proxy | ||||
|         elif "https_proxy" in os.environ: | ||||
|             del os.environ["https_proxy"] | ||||
| 
 | ||||
|     def testProxyFromArgs(self): | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost"), ("localhost", 0, None)) | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None)) | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost"), ("localhost", 0, None)) | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None)) | ||||
| 
 | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_auth=("a", "b")), | ||||
|             ("localhost", 0, ("a", "b"))) | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), | ||||
|             ("localhost", 3128, ("a", "b"))) | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_auth=("a", "b")), | ||||
|             ("localhost", 0, ("a", "b"))) | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), | ||||
|             ("localhost", 3128, ("a", "b"))) | ||||
| 
 | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, no_proxy=["example.com"], proxy_auth=("a", "b")), | ||||
|             ("localhost", 3128, ("a", "b"))) | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, no_proxy=["echo.websocket.org"], proxy_auth=("a", "b")), | ||||
|             (None, 0, None)) | ||||
| 
 | ||||
|     def testProxyFromEnv(self): | ||||
|         os.environ["http_proxy"] = "http://localhost/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None)) | ||||
|         os.environ["http_proxy"] = "http://localhost:3128/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None)) | ||||
| 
 | ||||
|         os.environ["http_proxy"] = "http://localhost/" | ||||
|         os.environ["https_proxy"] = "http://localhost2/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None)) | ||||
|         os.environ["http_proxy"] = "http://localhost:3128/" | ||||
|         os.environ["https_proxy"] = "http://localhost2:3128/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None)) | ||||
| 
 | ||||
|         os.environ["http_proxy"] = "http://localhost/" | ||||
|         os.environ["https_proxy"] = "http://localhost2/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, None)) | ||||
|         os.environ["http_proxy"] = "http://localhost:3128/" | ||||
|         os.environ["https_proxy"] = "http://localhost2:3128/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, None)) | ||||
| 
 | ||||
| 
 | ||||
|         os.environ["http_proxy"] = "http://a:b@localhost/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b"))) | ||||
|         os.environ["http_proxy"] = "http://a:b@localhost:3128/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b"))) | ||||
| 
 | ||||
|         os.environ["http_proxy"] = "http://a:b@localhost/" | ||||
|         os.environ["https_proxy"] = "http://a:b@localhost2/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b"))) | ||||
|         os.environ["http_proxy"] = "http://a:b@localhost:3128/" | ||||
|         os.environ["https_proxy"] = "http://a:b@localhost2:3128/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b"))) | ||||
| 
 | ||||
|         os.environ["http_proxy"] = "http://a:b@localhost/" | ||||
|         os.environ["https_proxy"] = "http://a:b@localhost2/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, ("a", "b"))) | ||||
|         os.environ["http_proxy"] = "http://a:b@localhost:3128/" | ||||
|         os.environ["https_proxy"] = "http://a:b@localhost2:3128/" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("a", "b"))) | ||||
| 
 | ||||
|         os.environ["http_proxy"] = "http://a:b@localhost/" | ||||
|         os.environ["https_proxy"] = "http://a:b@localhost2/" | ||||
|         os.environ["no_proxy"] = "example1.com,example2.com" | ||||
|         self.assertEqual(get_proxy_info("example.1.com", True), ("localhost2", None, ("a", "b"))) | ||||
|         os.environ["http_proxy"] = "http://a:b@localhost:3128/" | ||||
|         os.environ["https_proxy"] = "http://a:b@localhost2:3128/" | ||||
|         os.environ["no_proxy"] = "example1.com,example2.com, echo.websocket.org" | ||||
|         self.assertEqual(get_proxy_info("echo.websocket.org", True), (None, 0, None)) | ||||
| 
 | ||||
|         os.environ["http_proxy"] = "http://a:b@localhost:3128/" | ||||
|         os.environ["https_proxy"] = "http://a:b@localhost2:3128/" | ||||
|         os.environ["no_proxy"] = "127.0.0.0/8, 192.168.0.0/16" | ||||
|         self.assertEqual(get_proxy_info("127.0.0.1", False), (None, 0, None)) | ||||
|         self.assertEqual(get_proxy_info("192.168.1.1", False), (None, 0, None)) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     unittest.main() | ||||
| @ -0,0 +1,305 @@ | ||||
| Metadata-Version: 1.2 | ||||
| Name: websocket-client | ||||
| Version: 0.55.0 | ||||
| Summary: WebSocket client for Python. hybi13 is supported. | ||||
| Home-page: https://github.com/websocket-client/websocket-client.git | ||||
| Author: liris | ||||
| Author-email: liris.pp@gmail.com | ||||
| License: BSD | ||||
| Description: ================= | ||||
|         websocket-client | ||||
|         ================= | ||||
|          | ||||
|         websocket-client module  is WebSocket client for python. This provide the low level APIs for WebSocket. All APIs are the synchronous functions. | ||||
|          | ||||
|         websocket-client supports only hybi-13. | ||||
|          | ||||
|          | ||||
|         License | ||||
|         ======= | ||||
|          | ||||
|          - BSD | ||||
|          | ||||
|         Installation | ||||
|         ============ | ||||
|          | ||||
|         This module is tested on Python 2.7 and Python 3.4+. | ||||
|          | ||||
|         Type "python setup.py install" or "pip install websocket-client" to install. | ||||
|          | ||||
|         .. CAUTION:: | ||||
|          | ||||
|           from v0.16.0, we can install by "pip install websocket-client" for Python 3. | ||||
|          | ||||
|         This module depends on | ||||
|          | ||||
|          - six | ||||
|          - backports.ssl_match_hostname for Python 2.x | ||||
|          | ||||
|         Performance | ||||
|         ----------- | ||||
|          | ||||
|         The "send" and "validate_utf8" methods are too slow on pure python. If you want to get better performace, please install both numpy and wsaccel. | ||||
|          | ||||
|          | ||||
|         How about Python 3 | ||||
|         ================== | ||||
|          | ||||
|         Now, we support Python 3 on single source code from version 0.14.0. Thanks, @battlemidget and @ralphbean. | ||||
|          | ||||
|         HTTP Proxy | ||||
|         ========== | ||||
|          | ||||
|         Support websocket access via http proxy. | ||||
|         The proxy server must allow "CONNECT" method to websocket port. | ||||
|         Default squid setting is "ALLOWED TO CONNECT ONLY HTTPS PORT". | ||||
|          | ||||
|         Current implementation of websocket-client is using "CONNECT" method via proxy. | ||||
|          | ||||
|          | ||||
|         example | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             import websocket | ||||
|             ws = websocket.WebSocket() | ||||
|             ws.connect("ws://example.com/websocket", http_proxy_host="proxy_host_name", http_proxy_port=3128) | ||||
|          | ||||
|          | ||||
|          | ||||
|         Examples | ||||
|         ======== | ||||
|          | ||||
|         Long-lived connection | ||||
|         --------------------- | ||||
|         This example is similar to how WebSocket code looks in browsers using JavaScript. | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             import websocket | ||||
|             try: | ||||
|                 import thread | ||||
|             except ImportError: | ||||
|                 import _thread as thread | ||||
|             import time | ||||
|          | ||||
|             def on_message(ws, message): | ||||
|                 print(message) | ||||
|          | ||||
|             def on_error(ws, error): | ||||
|                 print(error) | ||||
|          | ||||
|             def on_close(ws): | ||||
|                 print("### closed ###") | ||||
|          | ||||
|             def on_open(ws): | ||||
|                 def run(*args): | ||||
|                     for i in range(3): | ||||
|                         time.sleep(1) | ||||
|                         ws.send("Hello %d" % i) | ||||
|                     time.sleep(1) | ||||
|                     ws.close() | ||||
|                     print("thread terminating...") | ||||
|                 thread.start_new_thread(run, ()) | ||||
|          | ||||
|          | ||||
|             if __name__ == "__main__": | ||||
|                 websocket.enableTrace(True) | ||||
|                 ws = websocket.WebSocketApp("ws://echo.websocket.org/", | ||||
|                                           on_message = on_message, | ||||
|                                           on_error = on_error, | ||||
|                                           on_close = on_close) | ||||
|                 ws.on_open = on_open | ||||
|                 ws.run_forever() | ||||
|          | ||||
|          | ||||
|          | ||||
|         Short-lived one-off send-receive | ||||
|         -------------------------------- | ||||
|         This is if you want to communicate a short message and disconnect immediately when done. | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             from websocket import create_connection | ||||
|             ws = create_connection("ws://echo.websocket.org/") | ||||
|             print("Sending 'Hello, World'...") | ||||
|             ws.send("Hello, World") | ||||
|             print("Sent") | ||||
|             print("Receiving...") | ||||
|             result =  ws.recv() | ||||
|             print("Received '%s'" % result) | ||||
|             ws.close() | ||||
|          | ||||
|          | ||||
|         If you want to customize socket options, set sockopt. | ||||
|          | ||||
|         sockopt example | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             from websocket import create_connection | ||||
|             ws = create_connection("ws://echo.websocket.org/", | ||||
|                                     sockopt=((socket.IPPROTO_TCP, socket.TCP_NODELAY),)) | ||||
|          | ||||
|          | ||||
|         More advanced: Custom class | ||||
|         --------------------------- | ||||
|         You can also write your own class for the connection, if you want to handle the nitty-gritty details yourself. | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             import socket | ||||
|             from websocket import create_connection, WebSocket | ||||
|             class MyWebSocket(WebSocket): | ||||
|                 def recv_frame(self): | ||||
|                     frame = super().recv_frame() | ||||
|                     print('yay! I got this frame: ', frame) | ||||
|                     return frame | ||||
|          | ||||
|             ws = create_connection("ws://echo.websocket.org/", | ||||
|                                     sockopt=((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),), class_=MyWebSocket) | ||||
|          | ||||
|          | ||||
|         FAQ | ||||
|         === | ||||
|          | ||||
|         How to disable ssl cert verification? | ||||
|         ------------------------------------- | ||||
|          | ||||
|         Please set sslopt to {"cert_reqs": ssl.CERT_NONE}. | ||||
|          | ||||
|         WebSocketApp sample | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             ws = websocket.WebSocketApp("wss://echo.websocket.org") | ||||
|             ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE}) | ||||
|          | ||||
|          | ||||
|         create_connection sample | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             ws = websocket.create_connection("wss://echo.websocket.org", | ||||
|               sslopt={"cert_reqs": ssl.CERT_NONE}) | ||||
|          | ||||
|          | ||||
|         WebSocket sample | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             ws = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE}) | ||||
|             ws.connect("wss://echo.websocket.org") | ||||
|          | ||||
|          | ||||
|         How to disable hostname verification? | ||||
|         ------------------------------------- | ||||
|          | ||||
|         Please set sslopt to {"check_hostname": False}. | ||||
|         (since v0.18.0) | ||||
|          | ||||
|         WebSocketApp sample | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             ws = websocket.WebSocketApp("wss://echo.websocket.org") | ||||
|             ws.run_forever(sslopt={"check_hostname": False}) | ||||
|          | ||||
|          | ||||
|         create_connection sample | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             ws = websocket.create_connection("wss://echo.websocket.org", | ||||
|               sslopt={"check_hostname": False}) | ||||
|          | ||||
|          | ||||
|         WebSocket sample | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             ws = websocket.WebSocket(sslopt={"check_hostname": False}) | ||||
|             ws.connect("wss://echo.websocket.org") | ||||
|          | ||||
|          | ||||
|         How to enable `SNI <http://en.wikipedia.org/wiki/Server_Name_Indication>`_? | ||||
|         --------------------------------------------------------------------------- | ||||
|          | ||||
|         SNI support is available for Python 2.7.9+ and 3.2+. It will be enabled automatically whenever possible. | ||||
|          | ||||
|          | ||||
|         Sub Protocols. | ||||
|         -------------- | ||||
|          | ||||
|         The server needs to support sub protocols, please set the subprotocol like this. | ||||
|          | ||||
|          | ||||
|         Subprotocol sample | ||||
|          | ||||
|         .. code:: python | ||||
|          | ||||
|             ws = websocket.create_connection("ws://example.com/websocket", subprotocols=["binary", "base64"]) | ||||
|          | ||||
|          | ||||
|          | ||||
|         wsdump.py | ||||
|         ========= | ||||
|          | ||||
|         wsdump.py is simple WebSocket test(debug) tool. | ||||
|          | ||||
|         sample for echo.websocket.org:: | ||||
|          | ||||
|           $ wsdump.py ws://echo.websocket.org/ | ||||
|           Press Ctrl+C to quit | ||||
|           > Hello, WebSocket | ||||
|           < Hello, WebSocket | ||||
|           > How are you? | ||||
|           < How are you? | ||||
|          | ||||
|          | ||||
|         Usage | ||||
|         ----- | ||||
|          | ||||
|         usage:: | ||||
|          | ||||
|           wsdump.py [-h] [-v [VERBOSE]] ws_url | ||||
|          | ||||
|          | ||||
|         WebSocket Simple Dump Tool | ||||
|          | ||||
|         positional arguments: | ||||
|           ws_url                websocket url. ex. ws://echo.websocket.org/ | ||||
|          | ||||
|          | ||||
|         optional arguments: | ||||
|           -h, --help                           show this help message and exit | ||||
|         WebSocketApp | ||||
|           -v VERBOSE, --verbose VERBOSE    set verbose mode. If set to 1, show opcode. If set to 2, enable to trace websocket module | ||||
|          | ||||
|          | ||||
|         example:: | ||||
|          | ||||
|           $ wsdump.py ws://echo.websocket.org/ | ||||
|           $ wsdump.py ws://echo.websocket.org/ -v | ||||
|           $ wsdump.py ws://echo.websocket.org/ -vv | ||||
|          | ||||
| Keywords: websockets | ||||
| Platform: UNKNOWN | ||||
| Classifier: Development Status :: 4 - Beta | ||||
| Classifier: License :: OSI Approved :: BSD License | ||||
| Classifier: Programming Language :: Python | ||||
| Classifier: Programming Language :: Python :: 2 | ||||
| Classifier: Programming Language :: Python :: 2.6 | ||||
| Classifier: Programming Language :: Python :: 2.7 | ||||
| Classifier: Programming Language :: Python :: 3 | ||||
| Classifier: Programming Language :: Python :: 3.4 | ||||
| Classifier: Programming Language :: Python :: 3.5 | ||||
| Classifier: Programming Language :: Python :: 3.6 | ||||
| Classifier: Programming Language :: Python :: 3.7 | ||||
| Classifier: Operating System :: MacOS :: MacOS X | ||||
| Classifier: Operating System :: POSIX | ||||
| Classifier: Operating System :: Microsoft :: Windows | ||||
| Classifier: Topic :: Internet | ||||
| Classifier: Topic :: Software Development :: Libraries :: Python Modules | ||||
| Classifier: Intended Audience :: Developers | ||||
| Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* | ||||
| @ -0,0 +1,32 @@ | ||||
| ChangeLog | ||||
| LICENSE | ||||
| MANIFEST.in | ||||
| README.rst | ||||
| setup.cfg | ||||
| setup.py | ||||
| bin/wsdump.py | ||||
| examples/echo_client.py | ||||
| examples/echoapp_client.py | ||||
| websocket/__init__.py | ||||
| websocket/_abnf.py | ||||
| websocket/_app.py | ||||
| websocket/_cookiejar.py | ||||
| websocket/_core.py | ||||
| websocket/_exceptions.py | ||||
| websocket/_handshake.py | ||||
| websocket/_http.py | ||||
| websocket/_logging.py | ||||
| websocket/_socket.py | ||||
| websocket/_ssl_compat.py | ||||
| websocket/_url.py | ||||
| websocket/_utils.py | ||||
| websocket/tests/__init__.py | ||||
| websocket/tests/test_cookiejar.py | ||||
| websocket/tests/test_websocket.py | ||||
| websocket/tests/data/header01.txt | ||||
| websocket/tests/data/header02.txt | ||||
| websocket_client.egg-info/PKG-INFO | ||||
| websocket_client.egg-info/SOURCES.txt | ||||
| websocket_client.egg-info/dependency_links.txt | ||||
| websocket_client.egg-info/requires.txt | ||||
| websocket_client.egg-info/top_level.txt | ||||
| @ -0,0 +1,40 @@ | ||||
| ../../../../bin/wsdump.py | ||||
| ../websocket/__init__.py | ||||
| ../websocket/__init__.pyc | ||||
| ../websocket/_abnf.py | ||||
| ../websocket/_abnf.pyc | ||||
| ../websocket/_app.py | ||||
| ../websocket/_app.pyc | ||||
| ../websocket/_cookiejar.py | ||||
| ../websocket/_cookiejar.pyc | ||||
| ../websocket/_core.py | ||||
| ../websocket/_core.pyc | ||||
| ../websocket/_exceptions.py | ||||
| ../websocket/_exceptions.pyc | ||||
| ../websocket/_handshake.py | ||||
| ../websocket/_handshake.pyc | ||||
| ../websocket/_http.py | ||||
| ../websocket/_http.pyc | ||||
| ../websocket/_logging.py | ||||
| ../websocket/_logging.pyc | ||||
| ../websocket/_socket.py | ||||
| ../websocket/_socket.pyc | ||||
| ../websocket/_ssl_compat.py | ||||
| ../websocket/_ssl_compat.pyc | ||||
| ../websocket/_url.py | ||||
| ../websocket/_url.pyc | ||||
| ../websocket/_utils.py | ||||
| ../websocket/_utils.pyc | ||||
| ../websocket/tests/__init__.py | ||||
| ../websocket/tests/__init__.pyc | ||||
| ../websocket/tests/data/header01.txt | ||||
| ../websocket/tests/data/header02.txt | ||||
| ../websocket/tests/test_cookiejar.py | ||||
| ../websocket/tests/test_cookiejar.pyc | ||||
| ../websocket/tests/test_websocket.py | ||||
| ../websocket/tests/test_websocket.pyc | ||||
| PKG-INFO | ||||
| SOURCES.txt | ||||
| dependency_links.txt | ||||
| requires.txt | ||||
| top_level.txt | ||||
| @ -0,0 +1,2 @@ | ||||
| six | ||||
| backports.ssl_match_hostname | ||||
| @ -0,0 +1 @@ | ||||
| websocket | ||||
					Loading…
					
					
				
		Reference in new issue