websockets-3.4/0000755000076500000240000000000013146274747013473 5ustar mykstaff00000000000000websockets-3.4/LICENSE0000644000076500000240000000300012555415645014466 0ustar mykstaff00000000000000Copyright (c) 2013-2015 Aymeric Augustin and contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of websockets nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. websockets-3.4/MANIFEST.in0000644000076500000240000000004712661410171015212 0ustar mykstaff00000000000000include LICENSE graft websockets/py35 websockets-3.4/PKG-INFO0000644000076500000240000000436613146274747014601 0ustar mykstaff00000000000000Metadata-Version: 1.1 Name: websockets Version: 3.4 Summary: An implementation of the WebSocket Protocol (RFC 6455) Home-page: https://github.com/aaugustin/websockets Author: Aymeric Augustin Author-email: aymeric.augustin@m4x.org License: BSD Description: WebSockets ========== ``websockets`` is a library for developing WebSocket servers_ and clients_ in Python. It implements `RFC 6455`_ with a focus on correctness and simplicity. It passes the `Autobahn Testsuite`_. Built on top of Python's asynchronous I/O support introduced in `PEP 3156`_, it provides an API based on coroutines, making it easy to write highly concurrent applications. Installation is as simple as ``pip install websockets``. It requires Python ≥ 3.4 or Python 3.3 with the ``asyncio`` module, which is available with ``pip install asyncio``. Documentation is available on `Read the Docs`_. Bug reports, patches and suggestions welcome! Just open an issue_ or send a `pull request`_. .. _servers: https://github.com/aaugustin/websockets/blob/master/example/server.py .. _clients: https://github.com/aaugustin/websockets/blob/master/example/client.py .. _RFC 6455: http://tools.ietf.org/html/rfc6455 .. _Autobahn Testsuite: https://github.com/aaugustin/websockets/blob/master/compliance/README.rst .. _PEP 3156: http://www.python.org/dev/peps/pep-3156/ .. _Read the Docs: https://websockets.readthedocs.io/ .. _issue: https://github.com/aaugustin/websockets/issues/new .. _pull request: https://github.com/aaugustin/websockets/compare/ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 websockets-3.4/README.rst0000644000076500000240000000235612717420341015152 0ustar mykstaff00000000000000WebSockets ========== ``websockets`` is a library for developing WebSocket servers_ and clients_ in Python. It implements `RFC 6455`_ with a focus on correctness and simplicity. It passes the `Autobahn Testsuite`_. Built on top of Python's asynchronous I/O support introduced in `PEP 3156`_, it provides an API based on coroutines, making it easy to write highly concurrent applications. Installation is as simple as ``pip install websockets``. It requires Python ≥ 3.4 or Python 3.3 with the ``asyncio`` module, which is available with ``pip install asyncio``. Documentation is available on `Read the Docs`_. Bug reports, patches and suggestions welcome! Just open an issue_ or send a `pull request`_. .. _servers: https://github.com/aaugustin/websockets/blob/master/example/server.py .. _clients: https://github.com/aaugustin/websockets/blob/master/example/client.py .. _RFC 6455: http://tools.ietf.org/html/rfc6455 .. _Autobahn Testsuite: https://github.com/aaugustin/websockets/blob/master/compliance/README.rst .. _PEP 3156: http://www.python.org/dev/peps/pep-3156/ .. _Read the Docs: https://websockets.readthedocs.io/ .. _issue: https://github.com/aaugustin/websockets/issues/new .. _pull request: https://github.com/aaugustin/websockets/compare/ websockets-3.4/setup.cfg0000644000076500000240000000034513146274747015316 0ustar mykstaff00000000000000[bdist_wheel] python-tag = py33.py34.py35.py36 [flake8] ignore = E731,F403,F405 [isort] known_standard_library = asyncio lines_after_imports = 2 multi_line_output = 5 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 websockets-3.4/setup.py0000644000076500000240000000341613146273110015170 0ustar mykstaff00000000000000import os.path import sys import setuptools root_dir = os.path.abspath(os.path.dirname(__file__)) description = "An implementation of the WebSocket Protocol (RFC 6455)" readme_file = os.path.join(root_dir, 'README.rst') with open(readme_file, encoding='utf-8') as f: long_description = f.read() version_module = os.path.join(root_dir, 'websockets', 'version.py') with open(version_module, encoding='utf-8') as f: exec(f.read()) py_version = sys.version_info[:2] if py_version < (3, 3): raise Exception("websockets requires Python >= 3.3.") packages = ['websockets'] if py_version >= (3, 5): packages.append('websockets/py35') ext_modules = [ setuptools.Extension( 'websockets.speedups', sources=['websockets/speedups.c'], optional=not os.path.exists(os.path.join(root_dir, '.cibuildwheel')), ) ] setuptools.setup( name='websockets', version=version, description=description, long_description=long_description, url='https://github.com/aaugustin/websockets', author='Aymeric Augustin', author_email='aymeric.augustin@m4x.org', license='BSD', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], packages=packages, ext_modules=ext_modules, extras_require={ ':python_version=="3.3"': ['asyncio'], }, ) websockets-3.4/websockets/0000755000076500000240000000000013146274747015644 5ustar mykstaff00000000000000websockets-3.4/websockets/__init__.py0000644000076500000240000000057712677037632017764 0ustar mykstaff00000000000000# This relies on each of the submodules having an __all__ variable. from .client import * from .exceptions import * from .protocol import * from .server import * from .uri import * from .version import version as __version__ # noqa __all__ = ( client.__all__ + exceptions.__all__ + protocol.__all__ + server.__all__ + uri.__all__ ) websockets-3.4/websockets/client.py0000644000076500000240000002021213146272615017461 0ustar mykstaff00000000000000""" The :mod:`websockets.client` module defines a simple WebSocket client API. """ import asyncio import collections.abc from .exceptions import InvalidHandshake, InvalidMessage, InvalidStatusCode from .handshake import build_request, check_response from .http import USER_AGENT, build_headers, read_response from .protocol import CONNECTING, OPEN, WebSocketCommonProtocol from .uri import parse_uri __all__ = ['connect', 'WebSocketClientProtocol'] class WebSocketClientProtocol(WebSocketCommonProtocol): """ Complete WebSocket client implementation as an :class:`asyncio.Protocol`. This class inherits most of its methods from :class:`~websockets.protocol.WebSocketCommonProtocol`. """ is_client = True state = CONNECTING @asyncio.coroutine def write_http_request(self, path, headers): """ Write request line and headers to the HTTP request. """ self.path = path self.request_headers = build_headers(headers) self.raw_request_headers = headers # Since the path and headers only contain ASCII characters, # we can keep this simple. request = ['GET {path} HTTP/1.1'.format(path=path)] request.extend('{}: {}'.format(k, v) for k, v in headers) request.append('\r\n') request = '\r\n'.join(request).encode() self.writer.write(request) @asyncio.coroutine def read_http_response(self): """ Read status line and headers from the HTTP response. Raise :exc:`~websockets.exceptions.InvalidMessage` if the HTTP message is malformed or isn't an HTTP/1.1 GET request. Don't attempt to read the response body because WebSocket handshake responses don't have one. If the response contains a body, it may be read from ``self.reader`` after this coroutine returns. """ try: status_code, headers = yield from read_response(self.reader) except ValueError as exc: raise InvalidMessage("Malformed HTTP message") from exc self.response_headers = build_headers(headers) self.raw_response_headers = headers return status_code, self.response_headers def process_subprotocol(self, get_header, subprotocols=None): """ Handle the Sec-WebSocket-Protocol HTTP header. """ subprotocol = get_header('Sec-WebSocket-Protocol') if subprotocol: if subprotocols is None or subprotocol not in subprotocols: raise InvalidHandshake( "Unknown subprotocol: {}".format(subprotocol)) return subprotocol @asyncio.coroutine def handshake(self, wsuri, origin=None, subprotocols=None, extra_headers=None): """ Perform the client side of the opening handshake. If provided, ``origin`` sets the Origin HTTP header. If provided, ``subprotocols`` is a list of supported subprotocols in order of decreasing preference. If provided, ``extra_headers`` sets additional HTTP request headers. It must be a mapping or an iterable of (name, value) pairs. """ headers = [] set_header = lambda k, v: headers.append((k, v)) if wsuri.port == (443 if wsuri.secure else 80): # pragma: no cover set_header('Host', wsuri.host) else: set_header('Host', '{}:{}'.format(wsuri.host, wsuri.port)) if origin is not None: set_header('Origin', origin) if subprotocols is not None: set_header('Sec-WebSocket-Protocol', ', '.join(subprotocols)) if extra_headers is not None: if isinstance(extra_headers, collections.abc.Mapping): extra_headers = extra_headers.items() for name, value in extra_headers: set_header(name, value) set_header('User-Agent', USER_AGENT) key = build_request(set_header) yield from self.write_http_request(wsuri.resource_name, headers) status_code, headers = yield from self.read_http_response() get_header = lambda k: headers.get(k, '') if status_code != 101: raise InvalidStatusCode(status_code) check_response(get_header, key) self.subprotocol = self.process_subprotocol(get_header, subprotocols) assert self.state == CONNECTING self.state = OPEN self.opening_handshake.set_result(True) @asyncio.coroutine def connect(uri, *, create_protocol=None, timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, loop=None, legacy_recv=False, klass=None, origin=None, subprotocols=None, extra_headers=None, **kwds): """ This coroutine connects to a WebSocket server at a given ``uri``. It yields a :class:`WebSocketClientProtocol` which can then be used to send and receive messages. :func:`connect` is a wrapper around the event loop's :meth:`~asyncio.BaseEventLoop.create_connection` method. Unknown keyword arguments are passed to :meth:`~asyncio.BaseEventLoop.create_connection`. For example, you can set the ``ssl`` keyword argument to a :class:`~ssl.SSLContext` to enforce some TLS settings. When connecting to a ``wss://`` URI, if this argument isn't provided explicitly, it's set to ``True``, which means Python's default :class:`~ssl.SSLContext` is used. The behavior of the ``timeout``, ``max_size``, and ``max_queue``, ``read_limit``, and ``write_limit`` optional arguments is described in the documentation of :class:`~websockets.protocol.WebSocketCommonProtocol`. The ``create_protocol`` parameter allows customizing the asyncio protocol that manages the connection. It should be a callable or class accepting the same arguments as :class:`WebSocketClientProtocol` and returning a :class:`WebSocketClientProtocol` instance. It defaults to :class:`WebSocketClientProtocol`. :func:`connect` also accepts the following optional arguments: * ``origin`` sets the Origin HTTP header * ``subprotocols`` is a list of supported subprotocols in order of decreasing preference * ``extra_headers`` sets additional HTTP request headers – it can be a mapping or an iterable of (name, value) pairs :func:`connect` raises :exc:`~websockets.uri.InvalidURI` if ``uri`` is invalid and :exc:`~websockets.handshake.InvalidHandshake` if the opening handshake fails. On Python 3.5, :func:`connect` can be used as a asynchronous context manager. In that case, the connection is closed when exiting the context. """ if loop is None: loop = asyncio.get_event_loop() # Backwards-compatibility: create_protocol used to be called klass. # In the unlikely event that both are specified, klass is ignored. if create_protocol is None: create_protocol = klass if create_protocol is None: create_protocol = WebSocketClientProtocol wsuri = parse_uri(uri) if wsuri.secure: kwds.setdefault('ssl', True) elif kwds.get('ssl') is not None: raise ValueError("connect() received a SSL context for a ws:// URI. " "Use a wss:// URI to enable TLS.") factory = lambda: create_protocol( host=wsuri.host, port=wsuri.port, secure=wsuri.secure, timeout=timeout, max_size=max_size, max_queue=max_queue, read_limit=read_limit, write_limit=write_limit, loop=loop, legacy_recv=legacy_recv, ) transport, protocol = yield from loop.create_connection( factory, wsuri.host, wsuri.port, **kwds) try: yield from protocol.handshake( wsuri, origin=origin, subprotocols=subprotocols, extra_headers=extra_headers) except Exception: yield from protocol.close_connection(force=True) raise return protocol try: from .py35.client import Connect except (SyntaxError, ImportError): # pragma: no cover pass else: Connect.__wrapped__ = connect # Copy over docstring to support building documentation on Python 3.5. Connect.__doc__ = connect.__doc__ connect = Connect websockets-3.4/websockets/compatibility.py0000644000076500000240000000303713146272615021062 0ustar mykstaff00000000000000import asyncio import http # Replace with BaseEventLoop.create_task when dropping Python < 3.4.2. try: # pragma: no cover asyncio_ensure_future = asyncio.ensure_future # Python ≥ 3.5 except AttributeError: # pragma: no cover asyncio_ensure_future = asyncio.async # Python < 3.5 try: # pragma: no cover # Python ≥ 3.5 SWITCHING_PROTOCOLS = http.HTTPStatus.SWITCHING_PROTOCOLS OK = http.HTTPStatus.OK BAD_REQUEST = http.HTTPStatus.BAD_REQUEST UNAUTHORIZED = http.HTTPStatus.UNAUTHORIZED FORBIDDEN = http.HTTPStatus.FORBIDDEN INTERNAL_SERVER_ERROR = http.HTTPStatus.INTERNAL_SERVER_ERROR SERVICE_UNAVAILABLE = http.HTTPStatus.SERVICE_UNAVAILABLE except AttributeError: # pragma: no cover # Python < 3.5 class SWITCHING_PROTOCOLS: value = 101 phrase = "Switching Protocols" class OK: value = 200 phrase = "OK" class BAD_REQUEST: value = 400 phrase = "Bad Request" class UNAUTHORIZED: value = 401 phrase = "Unauthorized" class FORBIDDEN: value = 403 phrase = "Forbidden" class INTERNAL_SERVER_ERROR: value = 500 phrase = "Internal Server Error" class SERVICE_UNAVAILABLE: value = 503 phrase = "Service Unavailable" websockets-3.4/websockets/exceptions.py0000644000076500000240000000440513146272615020372 0ustar mykstaff00000000000000__all__ = [ 'AbortHandshake', 'InvalidHandshake', 'InvalidMessage', 'InvalidOrigin', 'InvalidState', 'InvalidStatusCode', 'InvalidURI', 'ConnectionClosed', 'PayloadTooBig', 'WebSocketProtocolError', ] class InvalidHandshake(Exception): """ Exception raised when a handshake request or response is invalid. """ class AbortHandshake(InvalidHandshake): """ Exception raised to abort a handshake and return a HTTP response. """ def __init__(self, status, headers, body=None): self.status = status self.headers = headers self.body = body class InvalidMessage(InvalidHandshake): """ Exception raised when the HTTP message in a handshake request is malformed. """ class InvalidOrigin(InvalidHandshake): """ Exception raised when the origin in a handshake request is forbidden. """ class InvalidStatusCode(InvalidHandshake): """ Exception raised when a handshake response status code is invalid. Provides the integer status code in its ``status_code`` attribute. """ def __init__(self, status_code): self.status_code = status_code message = 'Status code not 101: {}'.format(status_code) super().__init__(message) class InvalidState(Exception): """ Exception raised when an operation is forbidden in the current state. """ class ConnectionClosed(InvalidState): """ Exception raised when trying to read or write on a closed connection. Provides the connection close code and reason in its ``code`` and ``reason`` attributes respectively. """ def __init__(self, code, reason): self.code = code self.reason = reason message = 'WebSocket connection is closed: ' message += 'code = {}, '.format(code) if code else 'no code, ' message += 'reason = {}.'.format(reason) if reason else 'no reason.' super().__init__(message) class InvalidURI(Exception): """ Exception raised when an URI isn't a valid websocket URI. """ class PayloadTooBig(Exception): """ Exception raised when a frame's payload exceeds the maximum size. """ class WebSocketProtocolError(Exception): """ Internal exception raised when the remote side breaks the protocol. """ websockets-3.4/websockets/framing.py0000644000076500000240000001370313146263120017624 0ustar mykstaff00000000000000""" The :mod:`websockets.framing` module implements data framing as specified in `section 5 of RFC 6455`_. It deals with a single frame at a time. Anything that depends on the sequence of frames is implemented in :mod:`websockets.protocol`. .. _section 5 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-5 """ import asyncio import collections import io import random import struct from .exceptions import PayloadTooBig, WebSocketProtocolError try: from .speedups import apply_mask except ImportError: # pragma: no cover from .utils import apply_mask __all__ = [ 'OP_CONT', 'OP_TEXT', 'OP_BINARY', 'OP_CLOSE', 'OP_PING', 'OP_PONG', 'Frame', 'read_frame', 'write_frame', 'parse_close', 'serialize_close' ] OP_CONT, OP_TEXT, OP_BINARY = range(0x00, 0x03) OP_CLOSE, OP_PING, OP_PONG = range(0x08, 0x0b) CLOSE_CODES = { 1000: "OK", 1001: "going away", 1002: "protocol error", 1003: "unsupported type", # 1004: - (reserved) # 1005: no status code (internal) # 1006: connection closed abnormally (internal) 1007: "invalid data", 1008: "policy violation", 1009: "message too big", 1010: "extension required", 1011: "unexpected error", # 1015: TLS failure (internal) } Frame = collections.namedtuple('Frame', ('fin', 'opcode', 'data')) Frame.__doc__ = """WebSocket frame. * ``fin`` is the FIN bit * ``opcode`` is the opcode * ``data`` is the payload data Only these three fields are needed by higher level code. The MASK bit, payload length and masking-key are handled on the fly by :func:`read_frame` and :func:`write_frame`. """ @asyncio.coroutine def read_frame(reader, mask, *, max_size=None): """ Read a WebSocket frame and return a :class:`Frame` object. ``reader`` is a coroutine taking an integer argument and reading exactly this number of bytes, unless the end of file is reached. ``mask`` is a :class:`bool` telling whether the frame should be masked i.e. whether the read happens on the server side. If ``max_size`` is set and the payload exceeds this size in bytes, :exc:`~websockets.exceptions.PayloadTooBig` is raised. This function validates the frame before returning it and raises :exc:`~websockets.exceptions.WebSocketProtocolError` if it contains incorrect values. """ # Read the header data = yield from reader(2) head1, head2 = struct.unpack('!BB', data) fin = bool(head1 & 0b10000000) if head1 & 0b01110000: raise WebSocketProtocolError("Reserved bits must be 0") opcode = head1 & 0b00001111 if bool(head2 & 0b10000000) != mask: raise WebSocketProtocolError("Incorrect masking") length = head2 & 0b01111111 if length == 126: data = yield from reader(2) length, = struct.unpack('!H', data) elif length == 127: data = yield from reader(8) length, = struct.unpack('!Q', data) if max_size is not None and length > max_size: raise PayloadTooBig("Payload exceeds limit " "({} > {} bytes)".format(length, max_size)) if mask: mask_bits = yield from reader(4) # Read the data data = yield from reader(length) if mask: data = apply_mask(data, mask_bits) frame = Frame(fin, opcode, data) check_frame(frame) return frame def write_frame(frame, writer, mask): """ Write a WebSocket frame. ``frame`` is the :class:`Frame` object to write. ``writer`` is a function accepting bytes. ``mask`` is a :class:`bool` telling whether the frame should be masked i.e. whether the write happens on the client side. This function validates the frame before sending it and raises :exc:`~websockets.exceptions.WebSocketProtocolError` if it contains incorrect values. """ check_frame(frame) output = io.BytesIO() # Prepare the header head1 = 0b10000000 if frame.fin else 0 head1 |= frame.opcode head2 = 0b10000000 if mask else 0 length = len(frame.data) if length < 0x7e: output.write(struct.pack('!BB', head1, head2 | length)) elif length < 0x10000: output.write(struct.pack('!BBH', head1, head2 | 126, length)) else: output.write(struct.pack('!BBQ', head1, head2 | 127, length)) if mask: mask_bits = struct.pack('!I', random.getrandbits(32)) output.write(mask_bits) # Prepare the data if mask: data = apply_mask(frame.data, mask_bits) else: data = frame.data output.write(data) # Send the frame writer(output.getvalue()) def check_frame(frame): """ Raise :exc:`~websockets.exceptions.WebSocketProtocolError` if the frame contains incorrect values. """ if frame.opcode in (OP_CONT, OP_TEXT, OP_BINARY): return elif frame.opcode in (OP_CLOSE, OP_PING, OP_PONG): if len(frame.data) > 125: raise WebSocketProtocolError("Control frame too long") if not frame.fin: raise WebSocketProtocolError("Fragmented control frame") else: raise WebSocketProtocolError("Invalid opcode") def parse_close(data): """ Parse the data in a close frame. Return ``(code, reason)`` when ``code`` is an :class:`int` and ``reason`` a :class:`str`. Raise :exc:`~websockets.exceptions.WebSocketProtocolError` or :exc:`UnicodeDecodeError` if the data is invalid. """ length = len(data) if length == 0: return 1005, '' elif length == 1: raise WebSocketProtocolError("Close frame too short") else: code, = struct.unpack('!H', data[:2]) if not (code in CLOSE_CODES or 3000 <= code < 5000): raise WebSocketProtocolError("Invalid status code") reason = data[2:].decode('utf-8') return code, reason def serialize_close(code, reason): """ Serialize the data for a close frame. This is the reverse of :func:`parse_close`. """ return struct.pack('!H', code) + reason.encode('utf-8') websockets-3.4/websockets/handshake.py0000644000076500000240000001032613146263120020125 0ustar mykstaff00000000000000""" The :mod:`websockets.handshake` module deals with the WebSocket opening handshake according to `section 4 of RFC 6455`_. .. _section 4 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-4 It provides functions to implement the handshake with any existing HTTP library. You must pass to these functions: - A ``set_header`` function accepting a header name and a header value, - A ``get_header`` function accepting a header name and returning the header value. The inputs and outputs of ``get_header`` and ``set_header`` are :class:`str` objects containing only ASCII characters. Some checks cannot be performed because they depend too much on the context; instead, they're documented below. To accept a connection, a server must: - Read the request, check that the method is GET, and check the headers with :func:`check_request`, - Send a 101 response to the client with the headers created by :func:`build_response` if the request is valid; otherwise, send an appropriate HTTP error code. To open a connection, a client must: - Send a GET request to the server with the headers created by :func:`build_request`, - Read the response, check that the status code is 101, and check the headers with :func:`check_response`. """ import base64 import hashlib import random from .exceptions import InvalidHandshake __all__ = [ 'build_request', 'check_request', 'build_response', 'check_response', ] GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" def build_request(set_header): """ Build a handshake request to send to the server. Return the ``key`` which must be passed to :func:`check_response`. """ rand = bytes(random.getrandbits(8) for _ in range(16)) key = base64.b64encode(rand).decode() set_header('Upgrade', 'WebSocket') set_header('Connection', 'Upgrade') set_header('Sec-WebSocket-Key', key) set_header('Sec-WebSocket-Version', '13') return key def check_request(get_header): """ Check a handshake request received from the client. If the handshake is valid, this function returns the ``key`` which must be passed to :func:`build_response`. Otherwise it raises an :exc:`~websockets.exceptions.InvalidHandshake` exception and the server must return an error like 400 Bad Request. This function doesn't verify that the request is an HTTP/1.1 or higher GET request and doesn't perform Host and Origin checks. These controls are usually performed earlier in the HTTP request handling code. They're the responsibility of the caller. """ try: assert get_header('Upgrade').lower() == 'websocket' assert any( token.strip() == 'upgrade' for token in get_header('Connection').lower().split(',')) key = get_header('Sec-WebSocket-Key') assert len(base64.b64decode(key.encode(), validate=True)) == 16 assert get_header('Sec-WebSocket-Version') == '13' except Exception as exc: raise InvalidHandshake("Invalid request") from exc else: return key def build_response(set_header, key): """ Build a handshake response to send to the client. ``key`` comes from :func:`check_request`. """ set_header('Upgrade', 'WebSocket') set_header('Connection', 'Upgrade') set_header('Sec-WebSocket-Accept', accept(key)) def check_response(get_header, key): """ Check a handshake response received from the server. ``key`` comes from :func:`build_request`. If the handshake is valid, this function returns ``None``. Otherwise it raises an :exc:`~websockets.exceptions.InvalidHandshake` exception. This function doesn't verify that the response is an HTTP/1.1 or higher response with a 101 status code. These controls are the responsibility of the caller. """ try: assert get_header('Upgrade').lower() == 'websocket' assert any( token.strip() == 'upgrade' for token in get_header('Connection').lower().split(',')) assert get_header('Sec-WebSocket-Accept') == accept(key) except Exception as exc: raise InvalidHandshake("Invalid response") from exc def accept(key): sha1 = hashlib.sha1((key + GUID).encode()).digest() return base64.b64encode(sha1).decode() websockets-3.4/websockets/http.py0000644000076500000240000001442513146272615017173 0ustar mykstaff00000000000000""" The :mod:`websockets.http` module provides HTTP parsing functions. They're merely adequate for the WebSocket handshake messages. These functions cannot be imported from :mod:`websockets`; they must be imported from :mod:`websockets.http`. """ import asyncio import http.client import re import sys from .version import version as websockets_version __all__ = ['read_request', 'read_response', 'USER_AGENT'] MAX_HEADERS = 256 MAX_LINE = 4096 USER_AGENT = ' '.join(( 'Python/{}'.format(sys.version[:3]), 'websockets/{}'.format(websockets_version), )) # See https://tools.ietf.org/html/rfc7230#appendix-B. # Regex for validating header names. _token_re = re.compile(rb'^[-!#$%&\'*+.^_`|~0-9a-zA-Z]+$') # Regex for validating header values. # We don't attempt to support obsolete line folding. # Include HTAB (\x09), SP (\x20), VCHAR (\x21-\x7e), obs-text (\x80-\xff). # The ABNF is complicated because it attempts to express that optional # whitespace is ignored. We strip whitespace and don't revalidate that. # See also https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189 _value_re = re.compile(rb'^[\x09\x20-\x7e\x80-\xff]*$') @asyncio.coroutine def read_request(stream): """ Read an HTTP/1.1 GET request from ``stream``. ``stream`` is an :class:`~asyncio.StreamReader`. Return ``(path, headers)`` where ``path`` is a :class:`str` and ``headers`` is a list of ``(name, value)`` tuples. ``path`` isn't URL-decoded or validated in any way. Non-ASCII characters are represented with surrogate escapes. Raise an exception if the request isn't well formatted. Don't attempt to read the request body because WebSocket handshake requests don't have one. If the request contains a body, it may be read from ``stream`` after this coroutine returns. """ # https://tools.ietf.org/html/rfc7230#section-3.1.1 # Parsing is simple because fixed values are expected for method and # version and because path isn't checked. Since WebSocket software tends # to implement HTTP/1.1 strictly, there's little need for lenient parsing. # Given the implementation of read_line(), request_line ends with CRLF. request_line = yield from read_line(stream) # This may raise "ValueError: not enough values to unpack" method, path, version = request_line[:-2].split(b' ', 2) if method != b'GET': raise ValueError("Unsupported HTTP method: %r" % method) if version != b'HTTP/1.1': raise ValueError("Unsupported HTTP version: %r" % version) path = path.decode('ascii', 'surrogateescape') headers = yield from read_headers(stream) return path, headers @asyncio.coroutine def read_response(stream): """ Read an HTTP/1.1 response from ``stream``. ``stream`` is an :class:`~asyncio.StreamReader`. Return ``(status_code, headers)`` where ``status_code`` is a :class:`int` and ``headers`` is a list of ``(name, value)`` tuples. Non-ASCII characters are represented with surrogate escapes. Raise an exception if the response isn't well formatted. Don't attempt to read the response body, because WebSocket handshake responses don't have one. If the response contains a body, it may be read from ``stream`` after this coroutine returns. """ # https://tools.ietf.org/html/rfc7230#section-3.1.2 # As in read_request, parsing is simple because a fixed value is expected # for version, status_code is a 3-digit number, and reason can be ignored. # Given the implementation of read_line(), status_line ends with CRLF. status_line = yield from read_line(stream) # This may raise "ValueError: not enough values to unpack" version, status_code, reason = status_line[:-2].split(b' ', 2) if version != b'HTTP/1.1': raise ValueError("Unsupported HTTP version: %r" % version) # This may raise "ValueError: invalid literal for int() with base 10" status_code = int(status_code) if not 100 <= status_code < 1000: raise ValueError("Unsupported HTTP status_code code: %d" % status_code) if not _value_re.match(reason): raise ValueError("Invalid HTTP reason phrase: %r" % reason) headers = yield from read_headers(stream) return status_code, headers @asyncio.coroutine def read_headers(stream): """ Read HTTP headers from ``stream``. ``stream`` is an :class:`~asyncio.StreamReader`. Return ``(start_line, headers)`` where ``start_line`` is :class:`bytes` and ``headers`` is a list of ``(name, value)`` tuples. Non-ASCII characters are represented with surrogate escapes. """ # https://tools.ietf.org/html/rfc7230#section-3.2 # We don't attempt to support obsolete line folding. headers = [] for _ in range(MAX_HEADERS): line = yield from read_line(stream) if line == b'\r\n': break # This may raise "ValueError: not enough values to unpack" name, value = line[:-2].split(b':', 1) if not _token_re.match(name): raise ValueError("Invalid HTTP header name: %r" % name) value = value.strip(b' \t') if not _value_re.match(value): raise ValueError("Invalid HTTP header value: %r" % value) headers.append(( name.decode('ascii'), # guaranteed to be ASCII at this point value.decode('ascii', 'surrogateescape'), )) else: raise ValueError("Too many HTTP headers") return headers @asyncio.coroutine def read_line(stream): """ Read a single line from ``stream``. ``stream`` is an :class:`~asyncio.StreamReader`. """ # Security: this is bounded by the StreamReader's limit (default = 32kB). line = yield from stream.readline() # Security: this guarantees header values are small (hardcoded = 4kB) if len(line) > MAX_LINE: raise ValueError("Line too long") # Not mandatory but safe - https://tools.ietf.org/html/rfc7230#section-3.5 if not line.endswith(b'\r\n'): raise ValueError("Line without CRLF") return line def build_headers(raw_headers): """ Build a date structure for HTTP headers from a list of name - value pairs. See also https://github.com/aaugustin/websockets/issues/210. """ headers = http.client.HTTPMessage() headers._headers = raw_headers # HACK return headers websockets-3.4/websockets/protocol.py0000644000076500000240000006410613146263120020045 0ustar mykstaff00000000000000""" The :mod:`websockets.protocol` module handles WebSocket control and data frames as specified in `sections 4 to 8 of RFC 6455`_. .. _sections 4 to 8 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-4 """ import asyncio import asyncio.queues import codecs import collections import logging import random import struct from .compatibility import asyncio_ensure_future from .exceptions import ( ConnectionClosed, InvalidState, PayloadTooBig, WebSocketProtocolError ) from .framing import * from .handshake import * __all__ = ['WebSocketCommonProtocol'] logger = logging.getLogger(__name__) # A WebSocket connection goes through the following four states, in order: CONNECTING, OPEN, CLOSING, CLOSED = range(4) # In order to ensure consistency, the code always checks the current value of # WebSocketCommonProtocol.state before assigning a new value and never yields # between the check and the assignment. class WebSocketCommonProtocol(asyncio.StreamReaderProtocol): """ This class implements common parts of the WebSocket protocol. It assumes that the WebSocket connection is established. The handshake is managed in subclasses such as :class:`~websockets.server.WebSocketServerProtocol` and :class:`~websockets.client.WebSocketClientProtocol`. It runs a task that stores incoming data frames in a queue and deals with control frames automatically. It sends outgoing data frames and performs the closing handshake. The ``host``, ``port`` and ``secure`` parameters are simply stored as attributes for handlers that need them. The ``timeout`` parameter defines the maximum wait time in seconds for completing the closing handshake and, only on the client side, for terminating the TCP connection. :meth:`close()` will complete in at most this time on the server side and twice this time on the client side. The ``max_size`` parameter enforces the maximum size for incoming messages in bytes. The default value is 1MB. ``None`` disables the limit. If a message larger than the maximum size is received, :meth:`recv()` will raise :exc:`~websockets.exceptions.ConnectionClosed` and the connection will be closed with status code 1009. The ``max_queue`` parameter sets the maximum length of the queue that holds incoming messages. The default value is 32. 0 disables the limit. Messages are added to an in-memory queue when they're received; then :meth:`recv()` pops from that queue. In order to prevent excessive memory consumption when messages are received faster than they can be processed, the queue must be bounded. If the queue fills up, the protocol stops processing incoming data until :meth:`recv()` is called. In this situation, various receive buffers (at least in ``asyncio`` and in the OS) will fill up, then the TCP receive window will shrink, slowing down transmission to avoid packet loss. Since Python can use up to 4 bytes of memory to represent a single character, each websocket connection may use up to ``4 * max_size * max_queue`` bytes of memory to store incoming messages. By default, this is 128MB. You may want to lower the limits, depending on your application's requirements. The ``read_limit`` argument sets the high-water limit of the buffer for incoming bytes. The low-water limit is half the high-water limit. The default value is 64kB, half of asyncio's default (based on the current implementation of :class:`~asyncio.StreamReader`). The ``write_limit`` argument sets the high-water limit of the buffer for outgoing bytes. The low-water limit is a quarter of the high-water limit. The default value is 64kB, equal to asyncio's default (based on the current implementation of ``_FlowControlMixin``). As soon as the HTTP request and response in the opening handshake are processed, the request path is available in the :attr:`path` attribute, and the request and response HTTP headers are available: * as a :class:`~http.client.HTTPMessage` in the :attr:`request_headers` and :attr:`response_headers` attributes * as an iterable of (name, value) pairs in the :attr:`raw_request_headers` and :attr:`raw_response_headers` attributes These attributes must be treated as immutable. If a subprotocol was negotiated, it's available in the :attr:`subprotocol` attribute. Once the connection is closed, the status code is available in the :attr:`close_code` attribute and the reason in :attr:`close_reason`. """ # There are only two differences between the client-side and the server- # side behavior: masking the payload and closing the underlying TCP # connection. This class implements the server-side behavior by default. # To get the client-side behavior, set is_client = True. is_client = False state = OPEN def __init__(self, *, host=None, port=None, secure=None, timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, loop=None, legacy_recv=False): self.host = host self.port = port self.secure = secure self.timeout = timeout self.max_size = max_size self.max_queue = max_queue self.read_limit = read_limit self.write_limit = write_limit # Store a reference to loop to avoid relying on self._loop, a private # attribute of StreamReaderProtocol, inherited from _FlowControlMixin. if loop is None: loop = asyncio.get_event_loop() self.loop = loop self.legacy_recv = legacy_recv # This limit is both the line length limit and half the buffer limit. stream_reader = asyncio.StreamReader(limit=read_limit // 2, loop=loop) super().__init__(stream_reader, self.client_connected, loop) self.reader = None self.writer = None self._drain_lock = asyncio.Lock(loop=loop) self.path = None self.request_headers = None self.raw_request_headers = None self.response_headers = None self.raw_response_headers = None self.subprotocol = None # Code and reason must be set when the closing handshake completes. self.close_code = None self.close_reason = '' # Futures tracking steps in the connection's lifecycle. # Set to True when the opening handshake has completed properly. self.opening_handshake = asyncio.Future(loop=loop) # Set to True when the closing handshake has completed properly and to # False when the connection terminates abnormally. self.closing_handshake = asyncio.Future(loop=loop) # Set to None when the connection state becomes CLOSED. self.connection_closed = asyncio.Future(loop=loop) # Queue of received messages. self.messages = asyncio.queues.Queue(max_queue, loop=loop) # Mapping of ping IDs to waiters, in chronological order. self.pings = collections.OrderedDict() # Task managing the connection, initalized in self.client_connected. self.worker_task = None # In a subclass implementing the opening handshake, the state will be # CONNECTING at this point. if self.state == OPEN: self.opening_handshake.set_result(True) # Public API @property def local_address(self): """ Local address of the connection. This is a ``(host, port)`` tuple or ``None`` if the connection hasn't been established yet. """ if self.writer is None: return None return self.writer.get_extra_info('sockname') @property def remote_address(self): """ Remote address of the connection. This is a ``(host, port)`` tuple or ``None`` if the connection hasn't been established yet. """ if self.writer is None: return None return self.writer.get_extra_info('peername') @property def open(self): """ This property is ``True`` when the connection is usable. It may be used to detect disconnections but this is discouraged per the EAFP_ principle. When ``open`` is ``False``, using the connection raises a :exc:`~websockets.exceptions.ConnectionClosed` exception. .. _EAFP: https://docs.python.org/3/glossary.html#term-eafp """ return self.state == OPEN @property def state_name(self): """ Current connection state, as a string. Possible states are defined in the WebSocket specification: CONNECTING, OPEN, CLOSING, or CLOSED. To check if the connection is open, use :attr:`open` instead. """ return ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'][self.state] @asyncio.coroutine def close(self, code=1000, reason=''): """ This coroutine performs the closing handshake. It waits for the other end to complete the handshake. It doesn't do anything once the connection is closed. Thus it's idemptotent. It's safe to wrap this coroutine in :func:`~asyncio.ensure_future` since errors during connection termination aren't particularly useful. ``code`` must be an :class:`int` and ``reason`` a :class:`str`. """ if self.state == OPEN: # 7.1.2. Start the WebSocket Closing Handshake # 7.1.3. The WebSocket Closing Handshake is Started frame_data = serialize_close(code, reason) yield from self.write_frame(OP_CLOSE, frame_data) # If the connection doesn't terminate within the timeout, break out of # the worker loop. try: yield from asyncio.wait_for( self.worker_task, self.timeout, loop=self.loop) except asyncio.TimeoutError: self.worker_task.cancel() # The worker should terminate quickly once it has been cancelled. yield from self.worker_task @asyncio.coroutine def recv(self): """ This coroutine receives the next message. It returns a :class:`str` for a text frame and :class:`bytes` for a binary frame. When the end of the message stream is reached, :meth:`recv` raises :exc:`~websockets.exceptions.ConnectionClosed`. This can happen after a normal connection closure, a protocol error or a network failure. .. versionchanged:: 3.0 :meth:`recv` used to return ``None`` instead. Refer to the changelog for details. """ # Don't yield from self.ensure_open() here because messages could be # available in the queue even if the connection is closed. # Return any available message try: return self.messages.get_nowait() except asyncio.queues.QueueEmpty: pass # Don't yield from self.ensure_open() here because messages could be # received before the closing frame even if the connection is closing. # Wait for a message until the connection is closed next_message = asyncio_ensure_future( self.messages.get(), loop=self.loop) try: done, pending = yield from asyncio.wait( [next_message, self.worker_task], loop=self.loop, return_when=asyncio.FIRST_COMPLETED) except asyncio.CancelledError: # Handle the Task.cancel() next_message.cancel() raise # Now there's no need to yield from self.ensure_open(). Either a # message was received or the connection was closed. if next_message in done: return next_message.result() else: next_message.cancel() if not self.legacy_recv: raise ConnectionClosed(self.close_code, self.close_reason) @asyncio.coroutine def send(self, data): """ This coroutine sends a message. It sends :class:`str` as a text frame and :class:`bytes` as a binary frame. It raises a :exc:`TypeError` for other inputs. """ yield from self.ensure_open() if isinstance(data, str): opcode = 1 data = data.encode('utf-8') elif isinstance(data, bytes): opcode = 2 else: raise TypeError("data must be bytes or str") yield from self.write_frame(opcode, data) @asyncio.coroutine def ping(self, data=None): """ This coroutine sends a ping. It returns a :class:`~asyncio.Future` which will be completed when the corresponding pong is received and which you may ignore if you don't want to wait. A ping may serve as a keepalive or as a check that the remote endpoint received all messages up to this point, with ``yield from ws.ping()``. By default, the ping contains four random bytes. The content may be overridden with the optional ``data`` argument which must be of type :class:`str` (which will be encoded to UTF-8) or :class:`bytes`. """ yield from self.ensure_open() if data is not None: data = self.encode_data(data) # Protect against duplicates if a payload is explicitly set. if data in self.pings: raise ValueError("Already waiting for a pong with the same data") # Generate a unique random payload otherwise. while data is None or data in self.pings: data = struct.pack('!I', random.getrandbits(32)) self.pings[data] = asyncio.Future(loop=self.loop) yield from self.write_frame(OP_PING, data) return self.pings[data] @asyncio.coroutine def pong(self, data=b''): """ This coroutine sends a pong. An unsolicited pong may serve as a unidirectional heartbeat. The content may be overridden with the optional ``data`` argument which must be of type :class:`str` (which will be encoded to UTF-8) or :class:`bytes`. """ yield from self.ensure_open() data = self.encode_data(data) yield from self.write_frame(OP_PONG, data) # Private methods - no guarantees. def encode_data(self, data): # Expect str or bytes, return bytes. if isinstance(data, str): return data.encode('utf-8') elif isinstance(data, bytes): return data else: raise TypeError("data must be bytes or str") @asyncio.coroutine def ensure_open(self): # Raise a suitable exception if the connection isn't open. # Handle cases from the most common to the least common. if self.state == OPEN: return if self.state == CLOSED: raise ConnectionClosed(self.close_code, self.close_reason) # If the closing handshake is in progress, let it complete to get the # proper close status and code. As an safety measure, the timeout is # longer than the worst case (2 * self.timeout) but not unlimited. if self.state == CLOSING: yield from asyncio.wait_for( self.worker_task, 3 * self.timeout, loop=self.loop) raise ConnectionClosed(self.close_code, self.close_reason) # Control may only reach this point in buggy third-party subclasses. assert self.state == CONNECTING raise InvalidState("WebSocket connection isn't established yet.") @asyncio.coroutine def run(self): # This coroutine guarantees that the connection is closed at exit. yield from self.opening_handshake while not self.closing_handshake.done(): try: msg = yield from self.read_message() if msg is None: break yield from self.messages.put(msg) except asyncio.CancelledError: break except WebSocketProtocolError: yield from self.fail_connection(1002) except asyncio.IncompleteReadError: yield from self.fail_connection(1006) except UnicodeDecodeError: yield from self.fail_connection(1007) except PayloadTooBig: yield from self.fail_connection(1009) except Exception: yield from self.fail_connection(1011) raise yield from self.close_connection() @asyncio.coroutine def read_message(self): # Reassemble fragmented messages. frame = yield from self.read_data_frame(max_size=self.max_size) if frame is None: return if frame.opcode == OP_TEXT: text = True elif frame.opcode == OP_BINARY: text = False else: # frame.opcode == OP_CONT raise WebSocketProtocolError("Unexpected opcode") # Shortcut for the common case - no fragmentation if frame.fin: return frame.data.decode('utf-8') if text else frame.data # 5.4. Fragmentation chunks = [] max_size = self.max_size if text: decoder = codecs.getincrementaldecoder('utf-8')(errors='strict') if max_size is None: def append(frame): nonlocal chunks chunks.append(decoder.decode(frame.data, frame.fin)) else: def append(frame): nonlocal chunks, max_size chunks.append(decoder.decode(frame.data, frame.fin)) max_size -= len(frame.data) else: if max_size is None: def append(frame): nonlocal chunks chunks.append(frame.data) else: def append(frame): nonlocal chunks, max_size chunks.append(frame.data) max_size -= len(frame.data) append(frame) while not frame.fin: frame = yield from self.read_data_frame(max_size=max_size) if frame is None: raise WebSocketProtocolError("Incomplete fragmented message") if frame.opcode != OP_CONT: raise WebSocketProtocolError("Unexpected opcode") append(frame) return ('' if text else b'').join(chunks) @asyncio.coroutine def read_data_frame(self, max_size): # Deal with control frames automatically and return next data frame. # 6.2. Receiving Data while True: frame = yield from self.read_frame(max_size) # 5.5. Control Frames if frame.opcode == OP_CLOSE: # Make sure the close frame is valid before echoing it. code, reason = parse_close(frame.data) if self.state == OPEN: # 7.1.3. The WebSocket Closing Handshake is Started yield from self.write_frame(OP_CLOSE, frame.data) self.close_code, self.close_reason = code, reason self.closing_handshake.set_result(True) return elif frame.opcode == OP_PING: # Answer pings. yield from self.pong(frame.data) elif frame.opcode == OP_PONG: # Do not acknowledge pings on unsolicited pongs. if frame.data in self.pings: # Acknowledge all pings up to the one matching this pong. ping_id = None while ping_id != frame.data: ping_id, waiter = self.pings.popitem(0) if not waiter.cancelled(): waiter.set_result(None) # 5.6. Data Frames else: return frame @asyncio.coroutine def read_frame(self, max_size): is_masked = not self.is_client frame = yield from read_frame( self.reader.readexactly, is_masked, max_size=max_size) side = 'client' if self.is_client else 'server' logger.debug("%s << %s", side, frame) return frame @asyncio.coroutine def write_frame(self, opcode, data=b''): # Defensive assertion for protocol compliance. if self.state != OPEN: # pragma: no cover raise InvalidState("Cannot write to a WebSocket " "in the {} state".format(self.state_name)) # Make sure no other frame will be sent after a close frame. Do this # before yielding control to avoid sending more than one close frame. if opcode == OP_CLOSE: self.state = CLOSING frame = Frame(True, opcode, data) side = 'client' if self.is_client else 'server' logger.debug("%s >> %s", side, frame) is_masked = self.is_client write_frame(frame, self.writer.write, is_masked) # Backport of the combined logic of: # https://github.com/python/asyncio/pull/280 # https://github.com/python/asyncio/pull/291 # Remove when dropping support for Python < 3.6. transport = self.writer._transport if transport is not None: # pragma: no cover # PR 291 added the is_closing method to transports shortly after # PR 280 fixed the bug we're trying to work around in this block. if not hasattr(transport, 'is_closing'): # This emulates what is_closing would return if it existed. try: is_closing = transport._closing except AttributeError: is_closing = transport._closed if is_closing: yield try: # drain() cannot be called concurrently by multiple coroutines: # http://bugs.python.org/issue29930. Remove this lock when no # version of Python where this bugs exists is supported anymore. with (yield from self._drain_lock): # Handle flow control automatically. yield from self.writer.drain() except ConnectionError: # Terminate the connection if the socket died. yield from self.fail_connection(1006) # And raise an exception, since the frame couldn't be sent. raise ConnectionClosed(self.close_code, self.close_reason) @asyncio.coroutine def close_connection(self, force=False): # 7.1.1. Close the WebSocket Connection if self.state == CLOSED: return # Defensive assertion for protocol compliance. if self.state != CLOSING and not force: # pragma: no cover raise InvalidState("Cannot close a WebSocket connection " "in the {} state".format(self.state_name)) if self.is_client and not force: try: yield from asyncio.wait_for( self.connection_closed, self.timeout, loop=self.loop) except (asyncio.CancelledError, asyncio.TimeoutError): pass if self.state == CLOSED: return # Attempt to terminate the TCP connection properly. # If the socket is already closed, this may crash. try: if self.writer.can_write_eof(): self.writer.write_eof() except Exception: # pragma: no cover pass self.writer.close() try: yield from asyncio.wait_for( self.connection_closed, self.timeout, loop=self.loop) except (asyncio.CancelledError, asyncio.TimeoutError): pass @asyncio.coroutine def fail_connection(self, code=1011, reason=''): # 7.1.7. Fail the WebSocket Connection logger.info("Failing the WebSocket connection: %d %s", code, reason) if self.state == OPEN: if code == 1006: # Don't send a close frame if the connection is broken. Set # the state to CLOSING to allow close_connection to proceed. self.state = CLOSING else: frame_data = serialize_close(code, reason) yield from self.write_frame(OP_CLOSE, frame_data) if not self.closing_handshake.done(): self.close_code, self.close_reason = code, reason self.closing_handshake.set_result(False) yield from self.close_connection() # asyncio StreamReaderProtocol methods def client_connected(self, reader, writer): self.reader = reader self.writer = writer # Configure write buffer limit. self.writer._transport.set_write_buffer_limits(self.write_limit) # Start the task that handles incoming messages. self.worker_task = asyncio_ensure_future(self.run(), loop=self.loop) def eof_received(self): super().eof_received() # Since Python 3.5, StreamReaderProtocol.eof_received() returns True # to leave the transport open (http://bugs.python.org/issue24539). # This is inappropriate for websockets for at least three reasons. # 1. The use case is to read data until EOF with self.reader.read(-1). # Since websockets is a TLV protocol, this never happens. # 2. It doesn't work on SSL connections. A falsy value must be # returned to have the same behavior on SSL and plain connections. # 3. The websockets protocol has its own closing handshake. Endpoints # close the TCP connection after sending a Close frame. # As a consequence we revert to the previous, more useful behavior. return def connection_lost(self, exc): # 7.1.4. The WebSocket Connection is Closed self.state = CLOSED if not self.opening_handshake.done(): self.opening_handshake.set_result(False) if not self.closing_handshake.done(): self.close_code, self.close_reason = 1006, '' self.closing_handshake.set_result(False) if not self.connection_closed.done(): self.connection_closed.set_result(None) # Close the transport in case close_connection() wasn't executed. if self.writer is not None: self.writer.close() super().connection_lost(exc) websockets-3.4/websockets/py35/0000755000076500000240000000000013146274747016444 5ustar mykstaff00000000000000websockets-3.4/websockets/py35/__init__.py0000644000076500000240000000023012661410532020531 0ustar mykstaff00000000000000# This package contains code using async / await syntax added in Python 3.5. # It cannot be imported on Python < 3.5 because it triggers syntax errors. websockets-3.4/websockets/py35/__pycache__/0000755000076500000240000000000013146274747020654 5ustar mykstaff00000000000000websockets-3.4/websockets/py35/__pycache__/__init__.cpython-33.pyc0000644000076500000240000000023613134665725025034 0ustar mykstaff00000000000000 ZVc@sdS(N((((u?/Users/myk/Documents/dev/websockets/websockets/py35/__init__.pyuswebsockets-3.4/websockets/py35/__pycache__/__init__.cpython-34.pyc0000644000076500000240000000022213134670142025015 0ustar mykstaff00000000000000 ZV@sdS)Nrrr?/Users/myk/Documents/dev/websockets/websockets/py35/__init__.pyswebsockets-3.4/websockets/py35/__pycache__/__init__.cpython-35.pyc0000644000076500000240000000022213134670300025012 0ustar mykstaff00000000000000 ZV@sdS)Nrrr?/Users/myk/Documents/dev/websockets/websockets/py35/__init__.pyswebsockets-3.4/websockets/py35/__pycache__/__init__.cpython-36.pyc0000644000076500000240000000022213134667650025031 0ustar mykstaff000000000000003 ZV@sdS)Nrrr?/Users/myk/Documents/dev/websockets/websockets/py35/__init__.pyswebsockets-3.4/websockets/py35/__pycache__/client.cpython-35.pyc0000644000076500000240000000214313146263245024546 0ustar mykstaff00000000000000 QfY8@sGdddZdS)c@sLeZdZdZddZddZddZdd ZeZd S) Connectu This class wraps :func:`~websockets.client.connect` on Python ≥ 3.5. This allows using it as an asynchronous context manager. cOs|jj|||_dS)N) __class__ __wrapped__client)selfargskwargsr=/Users/myk/Documents/dev/websockets/websockets/py35/client.py__init__szConnect.__init__cs|IdH|_|jS)N) websocket)rrrr __aenter__ szConnect.__aenter__cs|jjIdHdS)N)r close)rexc_type exc_value tracebackrrr __aexit__szConnect.__aexit__ccs |jEdHS)N)r)rrrr __await__szConnect.__await__N) __name__ __module__ __qualname____doc__r r rr__iter__rrrr rs     rN)rrrrr swebsockets-3.4/websockets/py35/__pycache__/client.cpython-36.pyc0000644000076500000240000000207113146263124024543 0ustar mykstaff000000000000003 QfY8@sGdddZdS)c@s4eZdZdZddZddZddZdd ZeZd S) Connectu This class wraps :func:`~websockets.client.connect` on Python ≥ 3.5. This allows using it as an asynchronous context manager. cOs|jj|||_dS)N) __class__ __wrapped__client)selfargskwargsr=/Users/myk/Documents/dev/websockets/websockets/py35/client.py__init__szConnect.__init__cs|IdH|_|jS)N) websocket)rrrr __aenter__ s zConnect.__aenter__cs|jjIdHdS)N)r close)rexc_type exc_value tracebackrrr __aexit__szConnect.__aexit__ccs |jEdHS)N)r)rrrr __await__szConnect.__await__N) __name__ __module__ __qualname____doc__r r rr__iter__rrrr rs rN)rrrrr swebsockets-3.4/websockets/py35/__pycache__/client_server.cpython-35.pyc0000644000076500000240000000371713146263245026144 0ustar mykstaff00000000000000 QfY=@sGddlZddlTddlTddlmZGdddZdS)N)*)handlerc@s@eZdZddZddZddZddZd S) ClientServerContextManagercCs#tj|_tj|jdS)N)asyncionew_event_looploopset_event_loop)selfr D/Users/myk/Documents/dev/websockets/websockets/py35/client_server.pysetUp sz ClientServerContextManager.setUpcCs|jjdS)N)rclose)r r r r tearDownsz#ClientServerContextManager.tearDowncsvttdd}jj|_fdd}jj|jjjjjjdS)N localhosti!c sXtd4IdH:}|jdIdH|jIdH}j|dWdQIdHRXdS)Nzws://localhost:8642/zHello!)connectsendrecv assertEqual)clientreply)r r r run_clientsz:ClientServerContextManager.test_client..run_client)serverrrun_until_completeserverr wait_closed)r rrr )r r test_clients  z&ClientServerContextManager.test_clientcs)fdd}jj|dS)Nc smttdd4IdHItdIdH}|jdIdH|jIdH}j|dWdQIdHRXdS)Nri!zws://localhost:8642/zHello!)rrrrrr)rr)r r r run_server$s z:ClientServerContextManager.test_server..run_server)rr)r rr )r r test_server#sz&ClientServerContextManager.test_serverN)__name__ __module__ __qualname__r rrrr r r r r s    r)rrrZtest_client_serverrrr r r r s   websockets-3.4/websockets/py35/__pycache__/client_server.cpython-36.pyc0000644000076500000240000000353613146263124026140 0ustar mykstaff000000000000003 QfY=@s6ddlZddlTddlTddlmZGdddZdS)N)*)handlerc@s,eZdZddZddZddZddZd S) ClientServerContextManagercCstj|_tj|jdS)N)asyncionew_event_looploopset_event_loop)selfr D/Users/myk/Documents/dev/websockets/websockets/py35/client_server.pysetUp s z ClientServerContextManager.setUpcCs|jjdS)N)rclose)r r r r tearDownsz#ClientServerContextManager.tearDowncsTttdd}jj|_fdd}jj|jjjjjjdS)N localhosti!c sPtd4IdH0}|jdIdH|jIdH}j|dWdQIdHRXdS)Nzws://localhost:8642/zHello!)connectsendrecv assertEqual)clientreply)r r r run_clientsz:ClientServerContextManager.test_client..run_client)serverrrun_until_completeserverr wait_closed)r rrr )r r test_clients    z&ClientServerContextManager.test_clientcsfdd}jj|dS)Nc sbttdd4IdH>tdIdH}|jdIdH|jIdH}j|dWdQIdHRXdS)Nri!zws://localhost:8642/zHello!)rrrrrr)rr)r r r run_server$s z:ClientServerContextManager.test_server..run_server)rr)r rr )r r test_server#s z&ClientServerContextManager.test_serverN)__name__ __module__ __qualname__r rrrr r r r r sr)rrrZtest_client_serverrrr r r r s websockets-3.4/websockets/py35/__pycache__/server.cpython-35.pyc0000644000076500000240000000215513146263245024601 0ustar mykstaff00000000000000 QfYM@sGdddZdS)c@sLeZdZdZddZddZddZdd ZeZd S) Serveu This class wraps :func:`~websockets.server.serve` on Python ≥ 3.5. This allows using it as an asynchronous context manager. cOs|jj|||_dS)N) __class__ __wrapped__server)selfargskwargsr=/Users/myk/Documents/dev/websockets/websockets/py35/server.py__init__szServe.__init__cs|IdH|_|jS)N)r)rrrr __aenter__ szServe.__aenter__cs#|jj|jjIdHdS)N)rclose wait_closed)rexc_type exc_value tracebackrrr __aexit__s zServe.__aexit__ccs |jEdHS)N)r)rrrr __await__szServe.__await__N) __name__ __module__ __qualname____doc__r r rr__iter__rrrr rs     rN)rrrrr swebsockets-3.4/websockets/py35/__pycache__/server.cpython-36.pyc0000644000076500000240000000210013146263124024564 0ustar mykstaff000000000000003 QfYM@sGdddZdS)c@s4eZdZdZddZddZddZdd ZeZd S) Serveu This class wraps :func:`~websockets.server.serve` on Python ≥ 3.5. This allows using it as an asynchronous context manager. cOs|jj|||_dS)N) __class__ __wrapped__server)selfargskwargsr=/Users/myk/Documents/dev/websockets/websockets/py35/server.py__init__szServe.__init__cs|IdH|_|jS)N)r)rrrr __aenter__ s zServe.__aenter__cs|jj|jjIdHdS)N)rclose wait_closed)rexc_type exc_value tracebackrrr __aexit__s zServe.__aexit__ccs |jEdHS)N)r)rrrr __await__szServe.__await__N) __name__ __module__ __qualname____doc__r r rr__iter__rrrr rs rN)rrrrr swebsockets-3.4/websockets/py35/client.py0000644000076500000240000000107013146263121020252 0ustar mykstaff00000000000000class Connect: """ This class wraps :func:`~websockets.client.connect` on Python ≥ 3.5. This allows using it as an asynchronous context manager. """ def __init__(self, *args, **kwargs): self.client = self.__class__.__wrapped__(*args, **kwargs) async def __aenter__(self): self.websocket = await self return self.websocket async def __aexit__(self, exc_type, exc_value, traceback): await self.websocket.close() def __await__(self): return (yield from self.client) __iter__ = __await__ websockets-3.4/websockets/py35/client_server.py0000644000076500000240000000247513146263121021652 0ustar mykstaff00000000000000# Tests containing Python 3.5+ syntax, extracted from test_client_server.py. # To avoid test discovery, this module's name must not start with test_. import asyncio from ..client import * from ..server import * from ..test_client_server import handler class ClientServerContextManager: def setUp(self): self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) def tearDown(self): self.loop.close() def test_client(self): server = serve(handler, 'localhost', 8642) self.server = self.loop.run_until_complete(server) async def run_client(): async with connect('ws://localhost:8642/') as client: await client.send("Hello!") reply = await client.recv() self.assertEqual(reply, "Hello!") self.loop.run_until_complete(run_client()) self.server.close() self.loop.run_until_complete(self.server.wait_closed()) def test_server(self): async def run_server(): async with serve(handler, 'localhost', 8642): client = await connect('ws://localhost:8642/') await client.send("Hello!") reply = await client.recv() self.assertEqual(reply, "Hello!") self.loop.run_until_complete(run_server()) websockets-3.4/websockets/py35/server.py0000644000076500000240000000111513146263121020302 0ustar mykstaff00000000000000class Serve: """ This class wraps :func:`~websockets.server.serve` on Python ≥ 3.5. This allows using it as an asynchronous context manager. """ def __init__(self, *args, **kwargs): self.server = self.__class__.__wrapped__(*args, **kwargs) async def __aenter__(self): self.server = await self return self.server async def __aexit__(self, exc_type, exc_value, traceback): self.server.close() await self.server.wait_closed() def __await__(self): return (yield from self.server) __iter__ = __await__ websockets-3.4/websockets/server.py0000644000076500000240000005032313146272615017517 0ustar mykstaff00000000000000 """ The :mod:`websockets.server` module defines a simple WebSocket server API. """ import asyncio import collections.abc import logging from .compatibility import ( BAD_REQUEST, FORBIDDEN, INTERNAL_SERVER_ERROR, SERVICE_UNAVAILABLE, SWITCHING_PROTOCOLS, asyncio_ensure_future ) from .exceptions import ( AbortHandshake, InvalidHandshake, InvalidMessage, InvalidOrigin ) from .handshake import build_response, check_request from .http import USER_AGENT, build_headers, read_request from .protocol import CONNECTING, OPEN, WebSocketCommonProtocol __all__ = ['serve', 'WebSocketServerProtocol'] logger = logging.getLogger(__name__) class WebSocketServerProtocol(WebSocketCommonProtocol): """ Complete WebSocket server implementation as an :class:`asyncio.Protocol`. This class inherits most of its methods from :class:`~websockets.protocol.WebSocketCommonProtocol`. For the sake of simplicity, it doesn't rely on a full HTTP implementation. Its support for HTTP responses is very limited. """ state = CONNECTING def __init__(self, ws_handler, ws_server, *, origins=None, subprotocols=None, extra_headers=None, **kwds): self.ws_handler = ws_handler self.ws_server = ws_server self.origins = origins self.subprotocols = subprotocols self.extra_headers = extra_headers super().__init__(**kwds) def connection_made(self, transport): super().connection_made(transport) # Register the connection with the server when creating the handler # task. (Registering at the beginning of the handler coroutine would # create a race condition between the creation of the task, which # schedules its execution, and the moment the handler starts running.) self.ws_server.register(self) self.handler_task = asyncio_ensure_future( self.handler(), loop=self.loop) @asyncio.coroutine def handler(self): # Since this method doesn't have a caller able to handle exceptions, # it attemps to log relevant ones and close the connection properly. try: try: path = yield from self.handshake( origins=self.origins, subprotocols=self.subprotocols, extra_headers=self.extra_headers) except ConnectionError as exc: logger.debug( "Connection error in opening handshake", exc_info=True) raise except Exception as exc: if self._is_server_shutting_down(exc): early_response = ( SERVICE_UNAVAILABLE, [], b"Server is shutting down.", ) elif isinstance(exc, AbortHandshake): early_response = ( exc.status, exc.headers, exc.body, ) elif isinstance(exc, InvalidOrigin): logger.warning("Invalid origin", exc_info=True) early_response = ( FORBIDDEN, [], str(exc).encode(), ) elif isinstance(exc, InvalidHandshake): logger.warning("Invalid handshake", exc_info=True) early_response = ( BAD_REQUEST, [], str(exc).encode(), ) else: logger.warning("Error in opening handshake", exc_info=True) early_response = ( INTERNAL_SERVER_ERROR, [], b"See server log for more information.", ) yield from self.write_http_response(*early_response) self.opening_handshake.set_result(False) yield from self.close_connection(force=True) return try: yield from self.ws_handler(self, path) except Exception as exc: if self._is_server_shutting_down(exc): yield from self.fail_connection(1001) else: logger.error("Error in connection handler", exc_info=True) yield from self.fail_connection(1011) raise try: yield from self.close() except ConnectionError as exc: logger.debug( "Connection error in closing handshake", exc_info=True) raise except Exception as exc: if not self._is_server_shutting_down(exc): logger.warning("Error in closing handshake", exc_info=True) raise except Exception: # Last-ditch attempt to avoid leaking connections on errors. try: self.writer.close() except Exception: # pragma: no cover pass finally: # Unregister the connection with the server when the handler task # terminates. Registration is tied to the lifecycle of the handler # task because the server waits for tasks attached to registered # connections before terminating. self.ws_server.unregister(self) def _is_server_shutting_down(self, exc): """ Decide whether an exception means that the server is shutting down. """ return ( isinstance(exc, asyncio.CancelledError) and self.ws_server.closing ) @asyncio.coroutine def read_http_request(self): """ Read request line and headers from the HTTP request. Raise :exc:`~websockets.exceptions.InvalidMessage` if the HTTP message is malformed or isn't an HTTP/1.1 GET request. Don't attempt to read the request body because WebSocket handshake requests don't have one. If the request contains a body, it may be read from ``self.reader`` after this coroutine returns. """ try: path, headers = yield from read_request(self.reader) except ValueError as exc: raise InvalidMessage("Malformed HTTP message") from exc self.path = path self.request_headers = build_headers(headers) self.raw_request_headers = headers return path, self.request_headers @asyncio.coroutine def write_http_response(self, status, headers, body=None): """ Write status line and headers to the HTTP response. This coroutine is also able to write a response body. """ self.response_headers = build_headers(headers) self.raw_response_headers = headers # Since the status line and headers only contain ASCII characters, # we can keep this simple. response = [ 'HTTP/1.1 {value} {phrase}'.format( value=status.value, phrase=status.phrase)] response.extend('{}: {}'.format(k, v) for k, v in headers) response.append('\r\n') response = '\r\n'.join(response).encode() self.writer.write(response) if body is not None: self.writer.write(body) @asyncio.coroutine def process_request(self, path, request_headers): """ Intercept the HTTP request and return an HTTP response if needed. ``request_headers`` are a :class:`~http.client.HTTPMessage`. If this coroutine returns ``None``, the WebSocket handshake continues. If it returns a status code, headers and a optionally a response body, that HTTP response is sent and the connection is closed. The HTTP status must be a :class:`~http.HTTPStatus`. HTTP headers must be an iterable of ``(name, value)`` pairs. If provided, the HTTP response body must be :class:`bytes`. (:class:`~http.HTTPStatus` was added in Python 3.5. Use a compatible object on earlier versions. Look at ``SWITCHING_PROTOCOLS`` in ``websockets.compatibility`` for an example.) This method may be overridden to check the request headers and set a different status, for example to authenticate the request and return ``HTTPStatus.UNAUTHORIZED`` or ``HTTPStatus.FORBIDDEN``. It is declared as a coroutine because such authentication checks are likely to require network requests. """ def process_origin(self, get_header, origins=None): """ Handle the Origin HTTP header. Raise :exc:`~websockets.exceptions.InvalidOrigin` if the origin isn't acceptable. """ origin = get_header('Origin') if origins is not None: if origin not in origins: raise InvalidOrigin("Origin not allowed: {}".format(origin)) return origin def process_subprotocol(self, get_header, subprotocols=None): """ Handle the Sec-WebSocket-Protocol HTTP header. """ if subprotocols is not None: subprotocol = get_header('Sec-WebSocket-Protocol') if subprotocol: return self.select_subprotocol( [p.strip() for p in subprotocol.split(',')], subprotocols, ) @staticmethod def select_subprotocol(client_protos, server_protos): """ Pick a subprotocol among those offered by the client. """ common_protos = set(client_protos) & set(server_protos) if not common_protos: return None priority = lambda p: client_protos.index(p) + server_protos.index(p) return sorted(common_protos, key=priority)[0] @asyncio.coroutine def handshake(self, origins=None, subprotocols=None, extra_headers=None): """ Perform the server side of the opening handshake. If provided, ``origins`` is a list of acceptable HTTP Origin values. Include ``''`` if the lack of an origin is acceptable. If provided, ``subprotocols`` is a list of supported subprotocols in order of decreasing preference. If provided, ``extra_headers`` sets additional HTTP response headers. It can be a mapping or an iterable of (name, value) pairs. It can also be a callable taking the request path and headers in arguments. Raise :exc:`~websockets.exceptions.InvalidHandshake` or a subclass if the handshake fails. Return the URI of the request. """ path, request_headers = yield from self.read_http_request() # Hook for customizing request handling, for example checking # authentication or treating some paths as plain HTTP endpoints. early_response = yield from self.process_request(path, request_headers) if early_response is not None: raise AbortHandshake(*early_response) get_header = lambda k: request_headers.get(k, '') key = check_request(get_header) self.origin = self.process_origin(get_header, origins) self.subprotocol = self.process_subprotocol(get_header, subprotocols) response_headers = [] set_header = lambda k, v: response_headers.append((k, v)) set_header('Server', USER_AGENT) if self.subprotocol: set_header('Sec-WebSocket-Protocol', self.subprotocol) if extra_headers is not None: if callable(extra_headers): extra_headers = extra_headers(path, self.raw_request_headers) if isinstance(extra_headers, collections.abc.Mapping): extra_headers = extra_headers.items() for name, value in extra_headers: set_header(name, value) build_response(set_header, key) yield from self.write_http_response( SWITCHING_PROTOCOLS, response_headers) assert self.state == CONNECTING self.state = OPEN self.opening_handshake.set_result(True) return path class WebSocketServer: """ Wraps an underlying :class:`~asyncio.Server` object. This class provides the return type of :func:`~websockets.server.serve`. This class shouldn't be instantiated directly. Objects of this class store a reference to an underlying :class:`~asyncio.Server` object returned by :meth:`~asyncio.AbstractEventLoop.create_server`. The class stores a reference rather than inheriting from :class:`~asyncio.Server` in part because :meth:`~asyncio.AbstractEventLoop.create_server` doesn't support passing a custom :class:`~asyncio.Server` class. :class:`WebSocketServer` supports cleaning up the underlying :class:`~asyncio.Server` object and other resources by implementing the interface of ``asyncio.events.AbstractServer``, namely its ``close()`` and ``wait_closed()`` methods. """ def __init__(self, loop): # Store a reference to loop to avoid relying on self.server._loop. self.loop = loop self.closing = False self.websockets = set() def wrap(self, server): """ Attach to a given :class:`~asyncio.Server`. Since :meth:`~asyncio.AbstractEventLoop.create_server` doesn't support injecting a custom ``Server`` class, a simple solution that doesn't rely on private APIs is to: - instantiate a :class:`WebSocketServer` - give the protocol factory a reference to that instance - call :meth:`~asyncio.AbstractEventLoop.create_server` with the factory - attach the resulting :class:`~asyncio.Server` with this method """ self.server = server def register(self, protocol): self.websockets.add(protocol) def unregister(self, protocol): self.websockets.remove(protocol) def close(self): """ Close the underlying server, and clean up connections. This calls :meth:`~asyncio.Server.close` on the underlying :class:`~asyncio.Server` object, closes open connections with status code 1001, and stops accepting new connections. """ # Make a note that the server is shutting down. Websocket connections # check this attribute to decide to send a "going away" close code. self.closing = True # Stop accepting new connections. self.server.close() # Close open connections. For each connection, two tasks are running: # 1. self.worker_task shuffles messages between the network and queues # 2. self.handler_task runs the opening handshake, the handler provided # by the user and the closing handshake # In the general case, cancelling the handler task will cause the # handler provided by the user to exit with a CancelledError, which # will then cause the worker task to terminate. for websocket in self.websockets: websocket.handler_task.cancel() @asyncio.coroutine def wait_closed(self): """ Wait until the underlying server and all connections are closed. This calls :meth:`~asyncio.Server.wait_closed` on the underlying :class:`~asyncio.Server` object and waits until closing handshakes are complete and all connections are closed. This method must be called after :meth:`close()`. """ # asyncio.wait doesn't accept an empty first argument. if self.websockets: # The handler or the worker task can terminate first, depending # on how the client behaves and the server is implemented. yield from asyncio.wait( [websocket.handler_task for websocket in self.websockets] + [websocket.worker_task for websocket in self.websockets], loop=self.loop) yield from self.server.wait_closed() @asyncio.coroutine def serve(ws_handler, host=None, port=None, *, create_protocol=None, timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, loop=None, legacy_recv=False, klass=None, origins=None, subprotocols=None, extra_headers=None, **kwds): """ Create, start, and return a :class:`WebSocketServer` object. :func:`serve` is a wrapper around the event loop's :meth:`~asyncio.AbstractEventLoop.create_server` method. Internally, the function creates and starts a :class:`~asyncio.Server` object by calling :meth:`~asyncio.AbstractEventLoop.create_server`. The :class:`WebSocketServer` keeps a reference to this object. The returned :class:`WebSocketServer` and its resources can be cleaned up by calling its :meth:`~websockets.server.WebSocketServer.close` and :meth:`~websockets.server.WebSocketServer.wait_closed` methods. On Python 3.5 and greater, :func:`serve` can also be used as an asynchronous context manager. In this case, the server is shut down when exiting the context. The ``ws_handler`` argument is the WebSocket handler. It must be a coroutine accepting two arguments: a :class:`WebSocketServerProtocol` and the request URI. The ``host`` and ``port`` arguments, as well as unrecognized keyword arguments, are passed along to :meth:`~asyncio.AbstractEventLoop.create_server`. For example, you can set the ``ssl`` keyword argument to a :class:`~ssl.SSLContext` to enable TLS. The ``create_protocol`` parameter allows customizing the asyncio protocol that manages the connection. It should be a callable or class accepting the same arguments as :class:`WebSocketServerProtocol` and returning a :class:`WebSocketServerProtocol` instance. It defaults to :class:`WebSocketServerProtocol`. The behavior of the ``timeout``, ``max_size``, and ``max_queue``, ``read_limit``, and ``write_limit`` optional arguments is described in the documentation of :class:`~websockets.protocol.WebSocketCommonProtocol`. :func:`serve` also accepts the following optional arguments: * ``origins`` defines acceptable Origin HTTP headers — include ``''`` if the lack of an origin is acceptable * ``subprotocols`` is a list of supported subprotocols in order of decreasing preference * ``extra_headers`` sets additional HTTP response headers — it can be a mapping, an iterable of (name, value) pairs, or a callable taking the request path and headers in arguments. Whenever a client connects, the server accepts the connection, creates a :class:`WebSocketServerProtocol`, performs the opening handshake, and delegates to the WebSocket handler. Once the handler completes, the server performs the closing handshake and closes the connection. Since there's no useful way to propagate exceptions triggered in handlers, they're sent to the ``'websockets.server'`` logger instead. Debugging is much easier if you configure logging to print them:: import logging logger = logging.getLogger('websockets.server') logger.setLevel(logging.ERROR) logger.addHandler(logging.StreamHandler()) """ if loop is None: loop = asyncio.get_event_loop() # Backwards-compatibility: create_protocol used to be called klass. # In the unlikely event that both are specified, klass is ignored. if create_protocol is None: create_protocol = klass if create_protocol is None: create_protocol = WebSocketServerProtocol ws_server = WebSocketServer(loop) secure = kwds.get('ssl') is not None factory = lambda: create_protocol( ws_handler, ws_server, host=host, port=port, secure=secure, timeout=timeout, max_size=max_size, max_queue=max_queue, read_limit=read_limit, write_limit=write_limit, loop=loop, legacy_recv=legacy_recv, origins=origins, subprotocols=subprotocols, extra_headers=extra_headers, ) server = yield from loop.create_server(factory, host, port, **kwds) ws_server.wrap(server) return ws_server try: from .py35.server import Serve except (SyntaxError, ImportError): # pragma: no cover pass else: Serve.__wrapped__ = serve # Copy over docstring to support building documentation on Python 3.5. Serve.__doc__ = serve.__doc__ serve = Serve websockets-3.4/websockets/speedups.c0000644000076500000240000000645213146250214017626 0ustar mykstaff00000000000000/* C implementation of performance sensitive functions. */ #define PY_SSIZE_T_CLEAN #include #include /* uint32_t, uint64_t */ #if __SSE2__ #include #endif static const Py_ssize_t MASK_LEN = 4; static PyObject * apply_mask(PyObject *self, PyObject *args, PyObject *kwds) { // Inputs are treated as immutable, which causes an extra memory copy. static char *kwlist[] = {"data", "mask", NULL}; const char *input; Py_ssize_t input_len; const char *mask; Py_ssize_t mask_len; // Initialize a PyBytesObject then get a pointer to the underlying char * // in order to avoid an extra memory copy in PyBytes_FromStringAndSize. PyObject *result; char *output; Py_ssize_t i = 0; if (!PyArg_ParseTupleAndKeywords( args, kwds, "y#y#", kwlist, &input, &input_len, &mask, &mask_len)) { return NULL; } if (mask_len != MASK_LEN) { PyErr_SetString(PyExc_ValueError, "mask must contain 4 bytes"); return NULL; } result = PyBytes_FromStringAndSize(NULL, input_len); if (result == NULL) { return NULL; } // Since we juste created result, we don't need error checks. output = PyBytes_AS_STRING(result); // Apparently GCC cannot figure out the following optimizations by itself. // We need a new scope for MSVC 2010 (non C99 friendly) { #if __SSE2__ // With SSE2 support, XOR by blocks of 16 bytes = 128 bits. // Since we cannot control the 16-bytes alignment of input and output // buffers, we rely on loadu/storeu rather than load/store. Py_ssize_t input_len_128 = input_len & ~15; __m128i mask_128 = _mm_set1_epi32(*(uint32_t *)mask); for (; i < input_len_128; i += 16) { __m128i in_128 = _mm_loadu_si128((__m128i *)(input + i)); __m128i out_128 = _mm_xor_si128(in_128, mask_128); _mm_storeu_si128((__m128i *)(output + i), out_128); } #else // Without SSE2 support, XOR by blocks of 8 bytes = 64 bits. // We assume the memory allocator aligns everything on 8 bytes boundaries. Py_ssize_t input_len_64 = input_len & ~7; uint32_t mask_32 = *(uint32_t *)mask; uint64_t mask_64 = ((uint64_t)mask_32 << 32) | (uint64_t)mask_32; for (; i < input_len_64; i += 8) { *(uint64_t *)(output + i) = *(uint64_t *)(input + i) ^ mask_64; } #endif } // XOR the remainder of the input byte by byte. for (; i < input_len; i++) { output[i] = input[i] ^ mask[i & (MASK_LEN - 1)]; } return result; } static PyMethodDef speedups_methods[] = { { "apply_mask", (PyCFunction)apply_mask, METH_VARARGS | METH_KEYWORDS, "Apply masking to websocket message.", }, {NULL, NULL, 0, NULL}, /* Sentinel */ }; static struct PyModuleDef speedups_module = { PyModuleDef_HEAD_INIT, "websocket.speedups", /* m_name */ "C implementation of performance sensitive functions.", /* m_doc */ -1, /* m_size */ speedups_methods, /* m_methods */ NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit_speedups(void) { return PyModule_Create(&speedups_module); } websockets-3.4/websockets/test_client_server.py0000644000076500000240000005556513146272615022131 0ustar mykstaff00000000000000import asyncio import contextlib import functools import logging import os import ssl import sys import unittest import unittest.mock import urllib.request from .client import * from .compatibility import FORBIDDEN, OK, UNAUTHORIZED from .exceptions import ConnectionClosed, InvalidHandshake, InvalidStatusCode from .http import USER_AGENT, read_response from .server import * # Avoid displaying stack traces at the ERROR logging level. logging.basicConfig(level=logging.CRITICAL) testcert = os.path.join(os.path.dirname(__file__), 'testcert.pem') @asyncio.coroutine def handler(ws, path): if path == '/attributes': yield from ws.send(repr((ws.host, ws.port, ws.secure))) elif path == '/path': yield from ws.send(str(ws.path)) elif path == '/headers': yield from ws.send(str(ws.request_headers)) yield from ws.send(str(ws.response_headers)) elif path == '/raw_headers': yield from ws.send(repr(ws.raw_request_headers)) yield from ws.send(repr(ws.raw_response_headers)) elif path == '/subprotocol': yield from ws.send(repr(ws.subprotocol)) else: yield from ws.send((yield from ws.recv())) @contextlib.contextmanager def temp_test_server(test, **kwds): test.start_server(**kwds) try: yield finally: test.stop_server() @contextlib.contextmanager def temp_test_client(test, *args, **kwds): test.start_client(*args, **kwds) try: yield finally: test.stop_client() def with_manager(manager, *args, **kwds): """ Return a decorator that wraps a function with a context manager. """ def decorate(func): @functools.wraps(func) def _decorate(self, *_args, **_kwds): with manager(self, *args, **kwds): return func(self, *_args, **_kwds) return _decorate return decorate def with_server(**kwds): """ Return a decorator for TestCase methods that starts and stops a server. """ return with_manager(temp_test_server, **kwds) def with_client(*args, **kwds): """ Return a decorator for TestCase methods that starts and stops a client. """ return with_manager(temp_test_client, *args, **kwds) class UnauthorizedServerProtocol(WebSocketServerProtocol): @asyncio.coroutine def process_request(self, path, request_headers): return UNAUTHORIZED, [] class ForbiddenServerProtocol(WebSocketServerProtocol): @asyncio.coroutine def process_request(self, path, request_headers): return FORBIDDEN, [] class HealthCheckServerProtocol(WebSocketServerProtocol): @asyncio.coroutine def process_request(self, path, request_headers): if path == '/__health__/': body = b'status = green\n' return OK, [('Content-Length', str(len(body)))], body class FooClientProtocol(WebSocketClientProtocol): pass class BarClientProtocol(WebSocketClientProtocol): pass class ClientServerTests(unittest.TestCase): secure = False def setUp(self): self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) def tearDown(self): self.loop.close() def run_loop_once(self): # Process callbacks scheduled with call_soon by appending a callback # to stop the event loop then running it until it hits that callback. self.loop.call_soon(self.loop.stop) self.loop.run_forever() def start_server(self, **kwds): server = serve(handler, 'localhost', 8642, **kwds) self.server = self.loop.run_until_complete(server) def start_client(self, path='', **kwds): client = connect('ws://localhost:8642/' + path, **kwds) self.client = self.loop.run_until_complete(client) def stop_client(self): try: self.loop.run_until_complete( asyncio.wait_for(self.client.worker_task, timeout=1)) except asyncio.TimeoutError: # pragma: no cover self.fail("Client failed to stop") def stop_server(self): self.server.close() try: self.loop.run_until_complete( asyncio.wait_for(self.server.wait_closed(), timeout=1)) except asyncio.TimeoutError: # pragma: no cover self.fail("Server failed to stop") @contextlib.contextmanager def temp_server(self, **kwds): with temp_test_server(self, **kwds): yield @contextlib.contextmanager def temp_client(self, *args, **kwds): with temp_test_client(self, *args, **kwds): yield @with_server() @with_client() def test_basic(self): self.loop.run_until_complete(self.client.send("Hello!")) reply = self.loop.run_until_complete(self.client.recv()) self.assertEqual(reply, "Hello!") @with_server() def test_server_close_while_client_connected(self): self.start_client() def test_explicit_event_loop(self): with self.temp_server(loop=self.loop): with self.temp_client(loop=self.loop): self.loop.run_until_complete(self.client.send("Hello!")) reply = self.loop.run_until_complete(self.client.recv()) self.assertEqual(reply, "Hello!") @with_server() @with_client('attributes') def test_protocol_attributes(self): expected_attrs = ('localhost', 8642, self.secure) client_attrs = (self.client.host, self.client.port, self.client.secure) self.assertEqual(client_attrs, expected_attrs) server_attrs = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_attrs, repr(expected_attrs)) @with_server() @with_client('path') def test_protocol_path(self): client_path = self.client.path self.assertEqual(client_path, '/path') server_path = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_path, '/path') @with_server() @with_client('headers') def test_protocol_headers(self): client_req = self.client.request_headers client_resp = self.client.response_headers self.assertEqual(client_req['User-Agent'], USER_AGENT) self.assertEqual(client_resp['Server'], USER_AGENT) server_req = self.loop.run_until_complete(self.client.recv()) server_resp = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_req, str(client_req)) self.assertEqual(server_resp, str(client_resp)) @with_server() @with_client('raw_headers') def test_protocol_raw_headers(self): client_req = self.client.raw_request_headers client_resp = self.client.raw_response_headers self.assertEqual(dict(client_req)['User-Agent'], USER_AGENT) self.assertEqual(dict(client_resp)['Server'], USER_AGENT) server_req = self.loop.run_until_complete(self.client.recv()) server_resp = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_req, repr(client_req)) self.assertEqual(server_resp, repr(client_resp)) @with_server() @with_client('raw_headers', extra_headers={'X-Spam': 'Eggs'}) def test_protocol_custom_request_headers_dict(self): req_headers = self.loop.run_until_complete(self.client.recv()) self.loop.run_until_complete(self.client.recv()) self.assertIn("('X-Spam', 'Eggs')", req_headers) @with_server() @with_client('raw_headers', extra_headers=[('X-Spam', 'Eggs')]) def test_protocol_custom_request_headers_list(self): req_headers = self.loop.run_until_complete(self.client.recv()) self.loop.run_until_complete(self.client.recv()) self.assertIn("('X-Spam', 'Eggs')", req_headers) @with_server(extra_headers=lambda p, r: {'X-Spam': 'Eggs'}) @with_client('raw_headers') def test_protocol_custom_response_headers_callable_dict(self): self.loop.run_until_complete(self.client.recv()) resp_headers = self.loop.run_until_complete(self.client.recv()) self.assertIn("('X-Spam', 'Eggs')", resp_headers) @with_server(extra_headers=lambda p, r: [('X-Spam', 'Eggs')]) @with_client('raw_headers') def test_protocol_custom_response_headers_callable_list(self): self.loop.run_until_complete(self.client.recv()) resp_headers = self.loop.run_until_complete(self.client.recv()) self.assertIn("('X-Spam', 'Eggs')", resp_headers) @with_server(extra_headers={'X-Spam': 'Eggs'}) @with_client('raw_headers') def test_protocol_custom_response_headers_dict(self): self.loop.run_until_complete(self.client.recv()) resp_headers = self.loop.run_until_complete(self.client.recv()) self.assertIn("('X-Spam', 'Eggs')", resp_headers) @with_server(extra_headers=[('X-Spam', 'Eggs')]) @with_client('raw_headers') def test_protocol_custom_response_headers_list(self): self.loop.run_until_complete(self.client.recv()) resp_headers = self.loop.run_until_complete(self.client.recv()) self.assertIn("('X-Spam', 'Eggs')", resp_headers) @with_server(create_protocol=HealthCheckServerProtocol) @with_client() def test_custom_protocol_http_request(self): # One URL returns an HTTP response. if self.secure: url = 'https://localhost:8642/__health__/' if sys.version_info[:2] < (3, 4): # pragma: no cover # Python 3.3 didn't check SSL certificates. open_health_check = functools.partial( urllib.request.urlopen, url) else: # pragma: no cover open_health_check = functools.partial( urllib.request.urlopen, url, context=self.client_context) else: url = 'http://localhost:8642/__health__/' open_health_check = functools.partial( urllib.request.urlopen, url) response = self.loop.run_until_complete( self.loop.run_in_executor(None, open_health_check)) with contextlib.closing(response): self.assertEqual(response.code, 200) self.assertEqual(response.read(), b'status = green\n') # Other URLs create a WebSocket connection. self.loop.run_until_complete(self.client.send("Hello!")) reply = self.loop.run_until_complete(self.client.recv()) self.assertEqual(reply, "Hello!") def assert_client_raises_code(self, status_code): with self.assertRaises(InvalidStatusCode) as raised: self.start_client() self.assertEqual(raised.exception.status_code, status_code) @with_server(create_protocol=UnauthorizedServerProtocol) def test_server_create_protocol(self): self.assert_client_raises_code(401) @with_server(create_protocol=(lambda *args, **kwargs: UnauthorizedServerProtocol(*args, **kwargs))) def test_server_create_protocol_function(self): self.assert_client_raises_code(401) @with_server(klass=UnauthorizedServerProtocol) def test_server_klass(self): self.assert_client_raises_code(401) @with_server(create_protocol=ForbiddenServerProtocol, klass=UnauthorizedServerProtocol) def test_server_create_protocol_over_klass(self): self.assert_client_raises_code(403) @with_server() @with_client('path', create_protocol=FooClientProtocol) def test_client_create_protocol(self): self.assertIsInstance(self.client, FooClientProtocol) @with_server() @with_client('path', create_protocol=( lambda *args, **kwargs: FooClientProtocol(*args, **kwargs))) def test_client_create_protocol_function(self): self.assertIsInstance(self.client, FooClientProtocol) @with_server() @with_client('path', klass=FooClientProtocol) def test_client_klass(self): self.assertIsInstance(self.client, FooClientProtocol) @with_server() @with_client('path', create_protocol=BarClientProtocol, klass=FooClientProtocol) def test_client_create_protocol_over_klass(self): self.assertIsInstance(self.client, BarClientProtocol) @with_server() @with_client('subprotocol') def test_no_subprotocol(self): server_subprotocol = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_subprotocol, repr(None)) self.assertEqual(self.client.subprotocol, None) @with_server(subprotocols=['superchat', 'chat']) @with_client('subprotocol', subprotocols=['otherchat', 'chat']) def test_subprotocol_found(self): server_subprotocol = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_subprotocol, repr('chat')) self.assertEqual(self.client.subprotocol, 'chat') @with_server(subprotocols=['superchat']) @with_client('subprotocol', subprotocols=['otherchat']) def test_subprotocol_not_found(self): server_subprotocol = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_subprotocol, repr(None)) self.assertEqual(self.client.subprotocol, None) @with_server() @with_client('subprotocol', subprotocols=['otherchat', 'chat']) def test_subprotocol_not_offered(self): server_subprotocol = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_subprotocol, repr(None)) self.assertEqual(self.client.subprotocol, None) @with_server(subprotocols=['superchat', 'chat']) @with_client('subprotocol') def test_subprotocol_not_requested(self): server_subprotocol = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_subprotocol, repr(None)) self.assertEqual(self.client.subprotocol, None) @with_server(subprotocols=['superchat']) @unittest.mock.patch.object(WebSocketServerProtocol, 'select_subprotocol') def test_subprotocol_error(self, _select_subprotocol): _select_subprotocol.return_value = 'superchat' with self.assertRaises(InvalidHandshake): self.start_client('subprotocol', subprotocols=['otherchat']) self.run_loop_once() @with_server() @unittest.mock.patch('websockets.server.read_request') def test_server_receives_malformed_request(self, _read_request): _read_request.side_effect = ValueError("read_request failed") with self.assertRaises(InvalidHandshake): self.start_client() @with_server() @unittest.mock.patch('websockets.client.read_response') def test_client_receives_malformed_response(self, _read_response): _read_response.side_effect = ValueError("read_response failed") with self.assertRaises(InvalidHandshake): self.start_client() self.run_loop_once() @with_server() @unittest.mock.patch('websockets.client.build_request') def test_client_sends_invalid_handshake_request(self, _build_request): def wrong_build_request(set_header): return '42' _build_request.side_effect = wrong_build_request with self.assertRaises(InvalidHandshake): self.start_client() @with_server() @unittest.mock.patch('websockets.server.build_response') def test_server_sends_invalid_handshake_response(self, _build_response): def wrong_build_response(set_header, key): return build_response(set_header, '42') _build_response.side_effect = wrong_build_response with self.assertRaises(InvalidHandshake): self.start_client() @with_server() @unittest.mock.patch('websockets.client.read_response') def test_server_does_not_switch_protocols(self, _read_response): @asyncio.coroutine def wrong_read_response(stream): status_code, headers = yield from read_response(stream) return 400, headers _read_response.side_effect = wrong_read_response with self.assertRaises(InvalidStatusCode): self.start_client() self.run_loop_once() @with_server() @unittest.mock.patch('websockets.server.WebSocketServerProtocol.send') def test_server_handler_crashes(self, send): send.side_effect = ValueError("send failed") with self.temp_client(): self.loop.run_until_complete(self.client.send("Hello!")) with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.client.recv()) # Connection ends with an unexpected error. self.assertEqual(self.client.close_code, 1011) @with_server() @unittest.mock.patch('websockets.server.WebSocketServerProtocol.close') def test_server_close_crashes(self, close): close.side_effect = ValueError("close failed") with self.temp_client(): self.loop.run_until_complete(self.client.send("Hello!")) reply = self.loop.run_until_complete(self.client.recv()) self.assertEqual(reply, "Hello!") # Connection ends with an abnormal closure. self.assertEqual(self.client.close_code, 1006) @with_server() @with_client() @unittest.mock.patch.object(WebSocketClientProtocol, 'handshake') def test_client_closes_connection_before_handshake(self, handshake): # We have mocked the handshake() method to prevent the client from # performing the opening handshake. Force it to close the connection. self.loop.run_until_complete(self.client.close_connection(force=True)) # The server should stop properly anyway. It used to hang because the # worker handling the connection was waiting for the opening handshake. @with_server() @unittest.mock.patch('websockets.server.read_request') def test_server_shuts_down_during_opening_handshake(self, _read_request): _read_request.side_effect = asyncio.CancelledError self.server.closing = True with self.assertRaises(InvalidHandshake) as raised: self.start_client() # Opening handshake fails with 503 Service Unavailable self.assertEqual(str(raised.exception), "Status code not 101: 503") @with_server() def test_server_shuts_down_during_connection_handling(self): with self.temp_client(): self.server.close() with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.client.recv()) # Websocket connection terminates with 1001 Going Away. self.assertEqual(self.client.close_code, 1001) @with_server(create_protocol=ForbiddenServerProtocol) def test_invalid_status_error_during_client_connect(self): with self.assertRaises(InvalidStatusCode) as raised: self.start_client() exception = raised.exception self.assertEqual(str(exception), "Status code not 101: 403") self.assertEqual(exception.status_code, 403) @with_server() @unittest.mock.patch('websockets.server.read_request') def test_connection_error_during_opening_handshake(self, _read_request): _read_request.side_effect = ConnectionError # Exception appears to be platform-dependent: InvalidHandshake on # macOS, ConnectionResetError on Linux. This doesn't matter; this # test primarily aims at covering a code path on the server side. with self.assertRaises(Exception): self.start_client() @with_server() @unittest.mock.patch('websockets.server.WebSocketServerProtocol.close') def test_connection_error_during_closing_handshake(self, close): close.side_effect = ConnectionError with self.temp_client(): self.loop.run_until_complete(self.client.send("Hello!")) reply = self.loop.run_until_complete(self.client.recv()) self.assertEqual(reply, "Hello!") # Connection ends with an abnormal closure. self.assertEqual(self.client.close_code, 1006) @unittest.skipUnless(os.path.exists(testcert), "test certificate is missing") class SSLClientServerTests(ClientServerTests): secure = True @property def server_context(self): ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) ssl_context.load_cert_chain(testcert) return ssl_context @property def client_context(self): ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) ssl_context.load_verify_locations(testcert) ssl_context.verify_mode = ssl.CERT_REQUIRED return ssl_context def start_server(self, *args, **kwds): kwds['ssl'] = self.server_context server = serve(handler, 'localhost', 8642, **kwds) self.server = self.loop.run_until_complete(server) def start_client(self, path='', **kwds): kwds['ssl'] = self.client_context client = connect('wss://localhost:8642/' + path, **kwds) self.client = self.loop.run_until_complete(client) @with_server() def test_ws_uri_is_rejected(self): client = connect('ws://localhost:8642/', ssl=self.client_context) with self.assertRaises(ValueError): self.loop.run_until_complete(client) class ClientServerOriginTests(unittest.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) def tearDown(self): self.loop.close() def test_checking_origin_succeeds(self): server = self.loop.run_until_complete( serve(handler, 'localhost', 8642, origins=['http://localhost'])) client = self.loop.run_until_complete( connect('ws://localhost:8642/', origin='http://localhost')) self.loop.run_until_complete(client.send("Hello!")) self.assertEqual(self.loop.run_until_complete(client.recv()), "Hello!") self.loop.run_until_complete(client.close()) server.close() self.loop.run_until_complete(server.wait_closed()) def test_checking_origin_fails(self): server = self.loop.run_until_complete( serve(handler, 'localhost', 8642, origins=['http://localhost'])) with self.assertRaisesRegex(InvalidHandshake, "Status code not 101: 403"): self.loop.run_until_complete( connect('ws://localhost:8642/', origin='http://otherhost')) server.close() self.loop.run_until_complete(server.wait_closed()) def test_checking_lack_of_origin_succeeds(self): server = self.loop.run_until_complete( serve(handler, 'localhost', 8642, origins=[''])) client = self.loop.run_until_complete(connect('ws://localhost:8642/')) self.loop.run_until_complete(client.send("Hello!")) self.assertEqual(self.loop.run_until_complete(client.recv()), "Hello!") self.loop.run_until_complete(client.close()) server.close() self.loop.run_until_complete(server.wait_closed()) try: from .py35.client_server import ClientServerContextManager except (SyntaxError, ImportError): # pragma: no cover pass else: class ClientServerContextManagerTests(ClientServerContextManager, unittest.TestCase): pass websockets-3.4/websockets/test_framing.py0000644000076500000240000001237313146263120020665 0ustar mykstaff00000000000000import asyncio import unittest import unittest.mock from .exceptions import PayloadTooBig, WebSocketProtocolError from .framing import * class FramingTests(unittest.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) def tearDown(self): self.loop.close() def decode(self, message, mask=False, max_size=None): self.stream = asyncio.StreamReader(loop=self.loop) self.stream.feed_data(message) self.stream.feed_eof() frame = self.loop.run_until_complete(read_frame( self.stream.readexactly, mask, max_size=max_size)) # Make sure all the data was consumed. self.assertTrue(self.stream.at_eof()) return frame def encode(self, frame, mask=False): writer = unittest.mock.Mock() write_frame(frame, writer, mask) # Ensure the entire frame is sent with a single call to writer(). # Multiple calls cause TCP fragmentation and degrade performance. self.assertEqual(writer.call_count, 1) # The frame data is the single positional argument of that call. return writer.call_args[0][0] def round_trip(self, message, expected, mask=False): decoded = self.decode(message, mask) self.assertEqual(decoded, expected) encoded = self.encode(decoded, mask) if mask: # non-deterministic encoding decoded = self.decode(encoded, mask) self.assertEqual(decoded, expected) else: # deterministic encoding self.assertEqual(encoded, message) def round_trip_close(self, data, code, reason): parsed = parse_close(data) self.assertEqual(parsed, (code, reason)) serialized = serialize_close(code, reason) self.assertEqual(serialized, data) def test_text(self): self.round_trip(b'\x81\x04Spam', Frame(True, OP_TEXT, b'Spam')) def test_text_masked(self): self.round_trip( b'\x81\x84\x5b\xfb\xe1\xa8\x08\x8b\x80\xc5', Frame(True, OP_TEXT, b'Spam'), mask=True) def test_binary(self): self.round_trip(b'\x82\x04Eggs', Frame(True, OP_BINARY, b'Eggs')) def test_binary_masked(self): self.round_trip( b'\x82\x84\x53\xcd\xe2\x89\x16\xaa\x85\xfa', Frame(True, OP_BINARY, b'Eggs'), mask=True) def test_non_ascii_text(self): self.round_trip( b'\x81\x05caf\xc3\xa9', Frame(True, OP_TEXT, 'café'.encode('utf-8'))) def test_non_ascii_text_masked(self): self.round_trip( b'\x81\x85\x64\xbe\xee\x7e\x07\xdf\x88\xbd\xcd', Frame(True, OP_TEXT, 'café'.encode('utf-8')), mask=True) def test_close(self): self.round_trip(b'\x88\x00', Frame(True, OP_CLOSE, b'')) def test_ping(self): self.round_trip(b'\x89\x04ping', Frame(True, OP_PING, b'ping')) def test_pong(self): self.round_trip(b'\x8a\x04pong', Frame(True, OP_PONG, b'pong')) def test_long(self): self.round_trip( b'\x82\x7e\x00\x7e' + 126 * b'a', Frame(True, OP_BINARY, 126 * b'a')) def test_very_long(self): self.round_trip( b'\x82\x7f\x00\x00\x00\x00\x00\x01\x00\x00' + 65536 * b'a', Frame(True, OP_BINARY, 65536 * b'a')) def test_payload_too_big(self): with self.assertRaises(PayloadTooBig): self.decode(b'\x82\x7e\x04\x01' + 1025 * b'a', max_size=1024) def test_bad_reserved_bits(self): with self.assertRaises(WebSocketProtocolError): self.decode(b'\xc0\x00') with self.assertRaises(WebSocketProtocolError): self.decode(b'\xa0\x00') with self.assertRaises(WebSocketProtocolError): self.decode(b'\x90\x00') def test_bad_opcode(self): for opcode in list(range(0x00, 0x03)) + list(range(0x08, 0x0b)): self.decode(bytes([0x80 | opcode, 0])) for opcode in list(range(0x03, 0x08)) + list(range(0x0b, 0x10)): with self.assertRaises(WebSocketProtocolError): self.decode(bytes([0x80 | opcode, 0])) def test_bad_mask_flag(self): self.decode(b'\x80\x80\x00\x00\x00\x00', mask=True) with self.assertRaises(WebSocketProtocolError): self.decode(b'\x80\x80\x00\x00\x00\x00') self.decode(b'\x80\x00') with self.assertRaises(WebSocketProtocolError): self.decode(b'\x80\x00', mask=True) def test_control_frame_too_long(self): with self.assertRaises(WebSocketProtocolError): self.decode(b'\x88\x7e\x00\x7e' + 126 * b'a') def test_fragmented_control_frame(self): with self.assertRaises(WebSocketProtocolError): self.decode(b'\x08\x00') def test_parse_close(self): self.round_trip_close(b'\x03\xe8', 1000, '') self.round_trip_close(b'\x03\xe8OK', 1000, 'OK') def test_parse_close_empty(self): self.assertEqual(parse_close(b''), (1005, '')) def test_parse_close_errors(self): with self.assertRaises(WebSocketProtocolError): parse_close(b'\x03') with self.assertRaises(WebSocketProtocolError): parse_close(b'\x03\xe7') with self.assertRaises(UnicodeDecodeError): parse_close(b'\x03\xe8\xff\xff') websockets-3.4/websockets/test_handshake.py0000644000076500000240000001017713146263120021170 0ustar mykstaff00000000000000import contextlib import unittest from .exceptions import InvalidHandshake from .handshake import * from .handshake import accept # private API class HandshakeTests(unittest.TestCase): def test_accept(self): # Test vector from RFC 6455 key = "dGhlIHNhbXBsZSBub25jZQ==" acc = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" self.assertEqual(accept(key), acc) def test_round_trip(self): request_headers = {} request_key = build_request(request_headers.__setitem__) response_key = check_request(request_headers.__getitem__) self.assertEqual(request_key, response_key) response_headers = {} build_response(response_headers.__setitem__, response_key) check_response(response_headers.__getitem__, request_key) @contextlib.contextmanager def assert_invalid_request_headers(self): """ Provide request headers for corruption. Assert that the transformation made them invalid. """ headers = {} build_request(headers.__setitem__) yield headers with self.assertRaises(InvalidHandshake): check_request(headers.__getitem__) def test_request_invalid_upgrade(self): with self.assert_invalid_request_headers() as headers: headers['Upgrade'] = 'socketweb' def test_request_missing_upgrade(self): with self.assert_invalid_request_headers() as headers: del headers['Upgrade'] def test_request_invalid_connection(self): with self.assert_invalid_request_headers() as headers: headers['Connection'] = 'Downgrade' def test_request_missing_connection(self): with self.assert_invalid_request_headers() as headers: del headers['Connection'] def test_request_invalid_key_not_base64(self): with self.assert_invalid_request_headers() as headers: headers['Sec-WebSocket-Key'] = "!@#$%^&*()" def test_request_invalid_key_not_well_padded(self): with self.assert_invalid_request_headers() as headers: headers['Sec-WebSocket-Key'] = "CSIRmL8dWYxeAdr/XpEHRw" def test_request_invalid_key_not_16_bytes_long(self): with self.assert_invalid_request_headers() as headers: headers['Sec-WebSocket-Key'] = "ZLpprpvK4PE=" def test_request_missing_key(self): with self.assert_invalid_request_headers() as headers: del headers['Sec-WebSocket-Key'] def test_request_invalid_version(self): with self.assert_invalid_request_headers() as headers: headers['Sec-WebSocket-Version'] = '42' def test_request_missing_version(self): with self.assert_invalid_request_headers() as headers: del headers['Sec-WebSocket-Version'] @contextlib.contextmanager def assert_invalid_response_headers(self, key='CSIRmL8dWYxeAdr/XpEHRw=='): """ Provide response headers for corruption. Assert that the transformation made them invalid. """ headers = {} build_response(headers.__setitem__, key) yield headers with self.assertRaises(InvalidHandshake): check_response(headers.__getitem__, key) def test_response_invalid_upgrade(self): with self.assert_invalid_response_headers() as headers: headers['Upgrade'] = 'socketweb' def test_response_missing_upgrade(self): with self.assert_invalid_response_headers() as headers: del headers['Upgrade'] def test_response_invalid_connection(self): with self.assert_invalid_response_headers() as headers: headers['Connection'] = 'Downgrade' def test_response_missing_connection(self): with self.assert_invalid_response_headers() as headers: del headers['Connection'] def test_response_invalid_accept(self): with self.assert_invalid_response_headers() as headers: other_key = "1Eq4UDEFQYg3YspNgqxv5g==" headers['Sec-WebSocket-Accept'] = accept(other_key) def test_response_missing_accept(self): with self.assert_invalid_response_headers() as headers: del headers['Sec-WebSocket-Accept'] websockets-3.4/websockets/test_http.py0000644000076500000240000001106713146263120020220 0ustar mykstaff00000000000000import asyncio import unittest from .http import * from .http import build_headers, read_headers class HTTPAsyncTests(unittest.TestCase): def setUp(self): super().setUp() self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.stream = asyncio.StreamReader(loop=self.loop) def tearDown(self): self.loop.close() super().tearDown() def test_read_request(self): # Example from the protocol overview in RFC 6455 self.stream.feed_data( b'GET /chat HTTP/1.1\r\n' b'Host: server.example.com\r\n' b'Upgrade: websocket\r\n' b'Connection: Upgrade\r\n' b'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n' b'Origin: http://example.com\r\n' b'Sec-WebSocket-Protocol: chat, superchat\r\n' b'Sec-WebSocket-Version: 13\r\n' b'\r\n' ) path, hdrs = self.loop.run_until_complete(read_request(self.stream)) self.assertEqual(path, '/chat') self.assertEqual(dict(hdrs)['Upgrade'], 'websocket') def test_read_response(self): # Example from the protocol overview in RFC 6455 self.stream.feed_data( b'HTTP/1.1 101 Switching Protocols\r\n' b'Upgrade: websocket\r\n' b'Connection: Upgrade\r\n' b'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n' b'Sec-WebSocket-Protocol: chat\r\n' b'\r\n' ) status_code, headers = self.loop.run_until_complete( read_response(self.stream)) self.assertEqual(status_code, 101) self.assertEqual(dict(headers)['Upgrade'], 'websocket') def test_request_method(self): self.stream.feed_data(b'OPTIONS * HTTP/1.1\r\n\r\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_request(self.stream)) def test_request_version(self): self.stream.feed_data(b'GET /chat HTTP/1.0\r\n\r\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_request(self.stream)) def test_response_version(self): self.stream.feed_data(b'HTTP/1.0 400 Bad Request\r\n\r\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_response(self.stream)) def test_response_status(self): self.stream.feed_data(b'HTTP/1.1 007 My name is Bond\r\n\r\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_response(self.stream)) def test_response_reason(self): self.stream.feed_data(b'HTTP/1.1 200 \x7f\r\n\r\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_response(self.stream)) def test_header_name(self): self.stream.feed_data(b'foo bar: baz qux\r\n\r\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_headers(self.stream)) def test_header_value(self): self.stream.feed_data(b'foo: \x00\x00\x0f\r\n\r\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_headers(self.stream)) def test_headers_limit(self): self.stream.feed_data(b'foo: bar\r\n' * 500 + b'\r\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_headers(self.stream)) def test_line_limit(self): self.stream.feed_data(b'a' * 5000 + b'\r\n\r\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_headers(self.stream)) def test_line_ending(self): self.stream.feed_data(b'foo: bar\n\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_headers(self.stream)) class HTTPSyncTests(unittest.TestCase): def test_build_headers(self): headers = build_headers([ ('X-Foo', 'Bar'), ('X-Baz', 'Quux Quux'), ]) self.assertEqual(headers['X-Foo'], 'Bar') self.assertEqual(headers['X-Bar'], None) self.assertEqual(headers.get('X-Bar', ''), '') self.assertEqual(headers.get('X-Baz', ''), 'Quux Quux') def test_build_headers_multi_value(self): headers = build_headers([ ('X-Foo', 'Bar'), ('X-Foo', 'Baz'), ]) # Getting a single value is non-deterministic. self.assertIn(headers['X-Foo'], ['Bar', 'Baz']) self.assertIn(headers.get('X-Foo'), ['Bar', 'Baz']) # Ordering is deterministic when getting all values. self.assertEqual(headers.get_all('X-Foo'), ['Bar', 'Baz']) websockets-3.4/websockets/test_protocol.py0000644000076500000240000007353413146263121021112 0ustar mykstaff00000000000000import asyncio import contextlib import functools import os import time import unittest import unittest.mock from .compatibility import asyncio_ensure_future from .exceptions import ConnectionClosed, InvalidState from .framing import * from .protocol import CLOSED, CONNECTING, WebSocketCommonProtocol # Unit for timeouts. May be increased on slow machines by setting the # WEBSOCKETS_TESTS_TIMEOUT_FACTOR environment variable. MS = 0.001 * int(os.environ.get('WEBSOCKETS_TESTS_TIMEOUT_FACTOR', 1)) # asyncio's debug mode has a 10x performance penalty for this test suite. if os.environ.get('PYTHONASYNCIODEBUG'): # pragma: no cover MS *= 10 # Ensure that timeouts are larger than the clock's resolution (for Windows). MS = max(MS, 2.5 * time.get_clock_info('monotonic').resolution) class TransportMock(unittest.mock.Mock): """ Transport mock to control the protocol's inputs and outputs in tests. It calls the protocol's connection_made and connection_lost methods like actual transports. To simulate incoming data, tests call the protocol's data_received and eof_received methods directly. They could also pause_writing and resume_writing to test flow control. """ # This should happen in __init__ but overriding Mock.__init__ is hard. def connect(self, loop, protocol): self.loop = loop self.protocol = protocol # Remove when dropping support for Python < 3.6. self._closing = False self.loop.call_soon(self.protocol.connection_made, self) def close(self): # Remove when dropping support for Python < 3.6. self._closing = True self.loop.call_soon(self.protocol.connection_lost, None) class CommonTests: """ Mixin that defines most tests but doesn't inherit unittest.TestCase. Tests are run by the ServerTests and ClientTests subclasses. """ def setUp(self): super().setUp() self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.protocol = WebSocketCommonProtocol() self.transport = TransportMock() self.transport.connect(self.loop, self.protocol) def tearDown(self): self.loop.run_until_complete( self.protocol.close_connection(force=True)) self.loop.close() super().tearDown() # Utilities for writing tests. def run_loop_once(self): # Process callbacks scheduled with call_soon by appending a callback # to stop the event loop then running it until it hits that callback. self.loop.call_soon(self.loop.stop) self.loop.run_forever() def make_drain_slow(self): # Process connection_made in order to initialize self.protocol.writer. self.run_loop_once() original_drain = self.protocol.writer.drain @asyncio.coroutine def delayed_drain(): yield from asyncio.sleep(3 * MS, loop=self.loop) yield from original_drain() self.protocol.writer.drain = delayed_drain close_frame = Frame(True, OP_CLOSE, serialize_close(1000, 'close')) local_close = Frame(True, OP_CLOSE, serialize_close(1000, 'local')) remote_close = Frame(True, OP_CLOSE, serialize_close(1000, 'remote')) @property def ensure_future(self): return functools.partial(asyncio_ensure_future, loop=self.loop) def receive_frame(self, frame): """ Make the protocol receive a frame. """ writer = self.protocol.data_received mask = not self.protocol.is_client self.loop.call_soon(write_frame, frame, writer, mask) def receive_eof(self): """ Make the protocol receive the end of stream. WebSocketCommonProtocol.eof_received returns None — it is inherited from StreamReaderProtocol. (Returning True wouldn't work on secure connections anyway.) As a consequence, actual transports close themselves after calling it. To emulate this behavior, this function closes the transport just after calling the protocol's eof_received. Closing the transport has the side-effect calling the protocol's connection_lost. """ self.loop.call_soon(self.protocol.eof_received) self.loop.call_soon(self.loop.call_soon, self.transport.close) def receive_eof_if_client(self): """ Like receive_eof, but only if this is the client side. Since the server is supposed to initiate the termination of the TCP connection, this method helps making tests work for both sides. """ if self.protocol.is_client: self.receive_eof() def close_connection(self, code=1000, reason='close'): """ Close the connection with a standard closing handshake. This puts the connection in the CLOSED state. """ close_frame_data = serialize_close(code, reason) # Prepare the response to the closing handshake from the remote side. self.receive_frame(Frame(True, OP_CLOSE, close_frame_data)) self.receive_eof_if_client() # Trigger the closing handshake from the local side and complete it. self.loop.run_until_complete(self.protocol.close(code, reason)) # Empty the outgoing data stream so we can make assertions later on. self.assertOneFrameSent(True, OP_CLOSE, close_frame_data) def close_connection_partial(self, code=1000, reason='close'): """ Initiate a standard closing handshake but do not complete it. The main difference with `close_connection` is that the connection is left in the CLOSING state until the event loop runs again. """ close_frame_data = serialize_close(code, reason) # Trigger the closing handshake from the local side. self.ensure_future(self.protocol.close(code, reason)) self.run_loop_once() # Empty the outgoing data stream so we can make assertions later on. self.assertOneFrameSent(True, OP_CLOSE, close_frame_data) # Prepare the response to the closing handshake from the remote side. self.receive_frame(Frame(True, OP_CLOSE, close_frame_data)) self.receive_eof_if_client() def process_invalid_frames(self): """ Make the protocol fail quickly after simulating invalid data. To achieve this, this function triggers the protocol's eof_received, which interrupts pending reads waiting for more data. It delays this operation with call_later because the protocol must start processing frames first. Otherwise it will see a closed connection and no data. """ self.loop.call_later(MS, self.receive_eof) with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.recv()) def process_control_frames(self): """ Process control frames received by the protocol. To ensure that recv completes quickly, receive an additional dummy frame, which recv() will drop. """ self.receive_frame(Frame(True, OP_TEXT, b'')) next_message = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(next_message, '') def last_sent_frame(self): """ Read the last frame sent to the transport. This method assumes that at most one frame was sent. It raises an AssertionError otherwise. """ stream = asyncio.StreamReader(loop=self.loop) for (data,), kw in self.transport.write.call_args_list: stream.feed_data(data) self.transport.write.call_args_list = [] stream.feed_eof() if stream.at_eof(): frame = None else: frame = self.loop.run_until_complete(read_frame( stream.readexactly, self.protocol.is_client)) if not stream.at_eof(): # pragma: no cover data = self.loop.run_until_complete(stream.read()) raise AssertionError("Trailing data found: {!r}".format(data)) return frame def assertOneFrameSent(self, fin, opcode, data): self.assertEqual(self.last_sent_frame(), Frame(fin, opcode, data)) def assertNoFrameSent(self): self.assertIsNone(self.last_sent_frame()) def assertConnectionClosed(self, code, message): # The following line guarantees that connection_lost was called. self.assertEqual(self.protocol.state, CLOSED) self.assertEqual(self.protocol.close_code, code) self.assertEqual(self.protocol.close_reason, message) @contextlib.contextmanager def assertCompletesWithin(self, min_time, max_time): t0 = self.loop.time() yield t1 = self.loop.time() dt = t1 - t0 self.assertGreaterEqual( dt, min_time, "Too fast: {} < {}".format(dt, min_time)) self.assertLess( dt, max_time, "Too slow: {} >= {}".format(dt, max_time)) # Test public attributes. def test_local_address(self): get_extra_info = unittest.mock.Mock(return_value=('host', 4312)) self.transport.get_extra_info = get_extra_info # The connection isn't established yet. self.assertEqual(self.protocol.local_address, None) self.run_loop_once() # The connection is established. self.assertEqual(self.protocol.local_address, ('host', 4312)) get_extra_info.assert_called_with('sockname', None) def test_remote_address(self): get_extra_info = unittest.mock.Mock(return_value=('host', 4312)) self.transport.get_extra_info = get_extra_info # The connection isn't established yet. self.assertEqual(self.protocol.remote_address, None) self.run_loop_once() # The connection is established. self.assertEqual(self.protocol.remote_address, ('host', 4312)) get_extra_info.assert_called_with('peername', None) def test_open(self): self.assertTrue(self.protocol.open) self.close_connection() self.assertFalse(self.protocol.open) def test_state_name(self): self.assertEqual(self.protocol.state_name, 'OPEN') self.close_connection() self.assertEqual(self.protocol.state_name, 'CLOSED') # Test the recv coroutine. def test_recv_text(self): self.receive_frame(Frame(True, OP_TEXT, 'café'.encode('utf-8'))) data = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(data, 'café') def test_recv_binary(self): self.receive_frame(Frame(True, OP_BINARY, b'tea')) data = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(data, b'tea') def test_recv_on_closing_connection(self): self.close_connection_partial() with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.recv()) def test_recv_on_closed_connection(self): self.close_connection() with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.recv()) def test_recv_protocol_error(self): self.receive_frame(Frame(True, OP_CONT, 'café'.encode('utf-8'))) self.process_invalid_frames() self.assertConnectionClosed(1002, '') def test_recv_unicode_error(self): self.receive_frame(Frame(True, OP_TEXT, 'café'.encode('latin-1'))) self.process_invalid_frames() self.assertConnectionClosed(1007, '') def test_recv_text_payload_too_big(self): self.protocol.max_size = 1024 self.receive_frame(Frame(True, OP_TEXT, 'café'.encode('utf-8') * 205)) self.process_invalid_frames() self.assertConnectionClosed(1009, '') def test_recv_binary_payload_too_big(self): self.protocol.max_size = 1024 self.receive_frame(Frame(True, OP_BINARY, b'tea' * 342)) self.process_invalid_frames() self.assertConnectionClosed(1009, '') def test_recv_text_no_max_size(self): self.protocol.max_size = None # for test coverage self.receive_frame(Frame(True, OP_TEXT, 'café'.encode('utf-8') * 205)) data = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(data, 'café' * 205) def test_recv_binary_no_max_size(self): self.protocol.max_size = None # for test coverage self.receive_frame(Frame(True, OP_BINARY, b'tea' * 342)) data = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(data, b'tea' * 342) def test_recv_other_error(self): @asyncio.coroutine def read_message(): raise Exception("BOOM") self.protocol.read_message = read_message self.process_invalid_frames() with self.assertRaises(Exception): self.loop.run_until_complete(self.protocol.worker_task) self.assertConnectionClosed(1011, '') def test_recv_cancelled(self): recv = self.ensure_future(self.protocol.recv()) self.loop.call_soon(recv.cancel) with self.assertRaises(asyncio.CancelledError): self.loop.run_until_complete(recv) # The next frame doesn't disappear in a vacuum (it used to). self.receive_frame(Frame(True, OP_TEXT, 'café'.encode('utf-8'))) data = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(data, 'café') # Test the send coroutine. def test_send_text(self): self.loop.run_until_complete(self.protocol.send('café')) self.assertOneFrameSent(True, OP_TEXT, 'café'.encode('utf-8')) def test_send_binary(self): self.loop.run_until_complete(self.protocol.send(b'tea')) self.assertOneFrameSent(True, OP_BINARY, b'tea') def test_send_type_error(self): with self.assertRaises(TypeError): self.loop.run_until_complete(self.protocol.send(42)) self.assertNoFrameSent() def test_send_on_closing_connection(self): self.close_connection_partial() with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.send('foobar')) self.assertNoFrameSent() def test_send_on_closed_connection(self): self.close_connection() with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.send('foobar')) self.assertNoFrameSent() # Test the ping coroutine. def test_ping_default(self): self.loop.run_until_complete(self.protocol.ping()) # With our testing tools, it's more convenient to extract the expected # ping data from the library's internals than from the frame sent. ping_data = next(iter(self.protocol.pings)) self.assertIsInstance(ping_data, bytes) self.assertEqual(len(ping_data), 4) self.assertOneFrameSent(True, OP_PING, ping_data) def test_ping_text(self): self.loop.run_until_complete(self.protocol.ping('café')) self.assertOneFrameSent(True, OP_PING, 'café'.encode('utf-8')) def test_ping_binary(self): self.loop.run_until_complete(self.protocol.ping(b'tea')) self.assertOneFrameSent(True, OP_PING, b'tea') def test_ping_type_error(self): with self.assertRaises(TypeError): self.loop.run_until_complete(self.protocol.ping(42)) self.assertNoFrameSent() def test_ping_on_closing_connection(self): self.close_connection_partial() with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.ping()) self.assertNoFrameSent() def test_ping_on_closed_connection(self): self.close_connection() with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.ping()) self.assertNoFrameSent() # Test the pong coroutine. def test_pong_default(self): self.loop.run_until_complete(self.protocol.pong()) self.assertOneFrameSent(True, OP_PONG, b'') def test_pong_text(self): self.loop.run_until_complete(self.protocol.pong('café')) self.assertOneFrameSent(True, OP_PONG, 'café'.encode('utf-8')) def test_pong_binary(self): self.loop.run_until_complete(self.protocol.pong(b'tea')) self.assertOneFrameSent(True, OP_PONG, b'tea') def test_pong_type_error(self): with self.assertRaises(TypeError): self.loop.run_until_complete(self.protocol.pong(42)) self.assertNoFrameSent() def test_pong_on_closing_connection(self): self.close_connection_partial() with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.pong()) self.assertNoFrameSent() def test_pong_on_closed_connection(self): self.close_connection() with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.pong()) self.assertNoFrameSent() # Test the protocol's logic for acknowledging pings with pongs. def test_answer_ping(self): self.receive_frame(Frame(True, OP_PING, b'test')) self.process_control_frames() self.assertOneFrameSent(True, OP_PONG, b'test') def test_ignore_pong(self): self.receive_frame(Frame(True, OP_PONG, b'test')) self.process_control_frames() self.assertNoFrameSent() def test_acknowledge_ping(self): ping = self.loop.run_until_complete(self.protocol.ping()) self.assertFalse(ping.done()) ping_frame = self.last_sent_frame() pong_frame = Frame(True, OP_PONG, ping_frame.data) self.receive_frame(pong_frame) self.process_control_frames() self.assertTrue(ping.done()) def test_acknowledge_previous_pings(self): pings = [( self.loop.run_until_complete(self.protocol.ping()), self.last_sent_frame(), ) for i in range(3)] # Unsolicited pong doesn't acknowledge pings self.receive_frame(Frame(True, OP_PONG, b'')) self.process_control_frames() self.assertFalse(pings[0][0].done()) self.assertFalse(pings[1][0].done()) self.assertFalse(pings[2][0].done()) # Pong acknowledges all previous pings self.receive_frame(Frame(True, OP_PONG, pings[1][1].data)) self.process_control_frames() self.assertTrue(pings[0][0].done()) self.assertTrue(pings[1][0].done()) self.assertFalse(pings[2][0].done()) def test_cancel_ping(self): ping = self.loop.run_until_complete(self.protocol.ping()) ping_frame = self.last_sent_frame() ping.cancel() pong_frame = Frame(True, OP_PONG, ping_frame.data) self.receive_frame(pong_frame) self.process_control_frames() self.assertTrue(ping.cancelled()) def test_duplicate_ping(self): self.loop.run_until_complete(self.protocol.ping(b'foobar')) self.assertOneFrameSent(True, OP_PING, b'foobar') with self.assertRaises(ValueError): self.loop.run_until_complete(self.protocol.ping(b'foobar')) self.assertNoFrameSent() # Test the protocol's logic for rebuilding fragmented messages. def test_fragmented_text(self): self.receive_frame(Frame(False, OP_TEXT, 'ca'.encode('utf-8'))) self.receive_frame(Frame(True, OP_CONT, 'fé'.encode('utf-8'))) data = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(data, 'café') def test_fragmented_binary(self): self.receive_frame(Frame(False, OP_BINARY, b't')) self.receive_frame(Frame(False, OP_CONT, b'e')) self.receive_frame(Frame(True, OP_CONT, b'a')) data = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(data, b'tea') def test_fragmented_text_payload_too_big(self): self.protocol.max_size = 1024 self.receive_frame(Frame(False, OP_TEXT, 'café'.encode('utf-8') * 100)) self.receive_frame(Frame(True, OP_CONT, 'café'.encode('utf-8') * 105)) self.process_invalid_frames() self.assertConnectionClosed(1009, '') def test_fragmented_binary_payload_too_big(self): self.protocol.max_size = 1024 self.receive_frame(Frame(False, OP_BINARY, b'tea' * 171)) self.receive_frame(Frame(True, OP_CONT, b'tea' * 171)) self.process_invalid_frames() self.assertConnectionClosed(1009, '') def test_fragmented_text_no_max_size(self): self.protocol.max_size = None # for test coverage self.receive_frame(Frame(False, OP_TEXT, 'café'.encode('utf-8') * 100)) self.receive_frame(Frame(True, OP_CONT, 'café'.encode('utf-8') * 105)) data = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(data, 'café' * 205) def test_fragmented_binary_no_max_size(self): self.protocol.max_size = None # for test coverage self.receive_frame(Frame(False, OP_BINARY, b'tea' * 171)) self.receive_frame(Frame(True, OP_CONT, b'tea' * 171)) data = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(data, b'tea' * 342) def test_control_frame_within_fragmented_text(self): self.receive_frame(Frame(False, OP_TEXT, 'ca'.encode('utf-8'))) self.receive_frame(Frame(True, OP_PING, b'')) self.receive_frame(Frame(True, OP_CONT, 'fé'.encode('utf-8'))) data = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(data, 'café') self.assertOneFrameSent(True, OP_PONG, b'') def test_unterminated_fragmented_text(self): self.receive_frame(Frame(False, OP_TEXT, 'ca'.encode('utf-8'))) # Missing the second part of the fragmented frame. self.receive_frame(Frame(True, OP_BINARY, b'tea')) self.process_invalid_frames() self.assertConnectionClosed(1002, '') def test_close_handshake_in_fragmented_text(self): self.receive_frame(Frame(False, OP_TEXT, 'ca'.encode('utf-8'))) self.receive_frame(Frame(True, OP_CLOSE, b'')) self.process_invalid_frames() self.assertConnectionClosed(1005, '') def test_connection_close_in_fragmented_text(self): self.receive_frame(Frame(False, OP_TEXT, 'ca'.encode('utf-8'))) self.process_invalid_frames() self.assertConnectionClosed(1006, '') # Test miscellaneous code paths to ensure full coverage. def test_connection_lost(self): # Test calling connection_lost without going through close_connection. self.protocol.connection_lost(None) self.assertConnectionClosed(1006, '') def test_ensure_connection_before_opening_handshake(self): self.protocol.state = CONNECTING with self.assertRaises(InvalidState): self.loop.run_until_complete(self.protocol.ensure_open()) def test_legacy_recv(self): # By default legacy_recv in disabled. self.assertEqual(self.protocol.legacy_recv, False) self.close_connection() # Enable legacy_recv. self.protocol.legacy_recv = True # Now recv() returns None instead of raising ConnectionClosed. self.assertIsNone(self.loop.run_until_complete(self.protocol.recv())) def test_connection_closed_attributes(self): self.close_connection() with self.assertRaises(ConnectionClosed) as context: self.loop.run_until_complete(self.protocol.recv()) connection_closed = context.exception self.assertEqual(connection_closed.code, 1000) self.assertEqual(connection_closed.reason, 'close') # Test the protocol logic for closing the connection. def test_local_close(self): # Emulate how the remote endpoint answers the closing handshake. self.receive_frame(self.close_frame) self.receive_eof_if_client() # Run the closing handshake. self.loop.run_until_complete(self.protocol.close(reason='close')) self.assertConnectionClosed(1000, 'close') self.assertOneFrameSent(*self.close_frame) # Closing the connection again is a no-op. self.loop.run_until_complete(self.protocol.close(reason='oh noes!')) self.assertConnectionClosed(1000, 'close') self.assertNoFrameSent() def test_remote_close(self): # Emulate how the remote endpoint initiates the closing handshake. self.receive_frame(self.close_frame) self.receive_eof_if_client() # Wait for some data in order to process the handshake. # After recv() raises ConnectionClosed, the connection is closed. with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.recv()) self.assertConnectionClosed(1000, 'close') self.assertOneFrameSent(*self.close_frame) # Closing the connection again is a no-op. self.loop.run_until_complete(self.protocol.close(reason='oh noes!')) self.assertConnectionClosed(1000, 'close') self.assertNoFrameSent() def test_simultaneous_close(self): self.receive_frame(self.remote_close) self.receive_eof_if_client() self.loop.run_until_complete(self.protocol.close(reason='local')) # The close code and reason are taken from the remote side because # that's presumably more useful that the values from the local side. self.assertConnectionClosed(1000, 'remote') self.assertOneFrameSent(*self.local_close) def test_close_preserves_incoming_frames(self): self.receive_frame(Frame(True, OP_TEXT, b'hello')) self.receive_frame(self.close_frame) self.receive_eof_if_client() self.loop.run_until_complete(self.protocol.close(reason='close')) self.assertConnectionClosed(1000, 'close') self.assertOneFrameSent(*self.close_frame) next_message = self.loop.run_until_complete(self.protocol.recv()) self.assertEqual(next_message, 'hello') def test_close_protocol_error(self): invalid_close_frame = Frame(True, OP_CLOSE, b'\x00') self.receive_frame(invalid_close_frame) self.receive_eof_if_client() self.loop.run_until_complete(self.protocol.close(reason='close')) self.assertConnectionClosed(1002, '') def test_close_connection_lost(self): self.receive_eof() self.loop.run_until_complete(self.protocol.close(reason='close')) self.assertConnectionClosed(1006, '') def test_remote_close_race_with_failing_connection(self): self.make_drain_slow() # Fail the connection while answering a close frame from the client. self.loop.call_soon(self.receive_frame, self.remote_close) self.loop.call_later( MS, self.ensure_future, self.protocol.fail_connection()) # The client expects the server to close the connection. # Simulate it instead of waiting for the connection timeout. self.loop.call_later(MS, self.receive_eof_if_client) with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.protocol.recv()) # The closing handshake was completed by fail_connection. self.assertConnectionClosed(1011, '') self.assertOneFrameSent(*self.remote_close) def test_local_close_during_recv(self): recv = self.ensure_future(self.protocol.recv()) self.receive_frame(self.close_frame) self.receive_eof_if_client() self.loop.run_until_complete(self.protocol.close(reason='close')) with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(recv) self.assertConnectionClosed(1000, 'close') # There is no test_remote_close_during_recv because it would be identical # to test_remote_close. def test_remote_close_during_send(self): self.make_drain_slow() send = self.ensure_future(self.protocol.send('hello')) self.receive_frame(self.close_frame) self.receive_eof() with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(send) self.assertConnectionClosed(1006, '') # There is no test_local_close_during_send because this cannot really # happen, considering that writes are serialized. class ServerTests(CommonTests, unittest.TestCase): def test_close_handshake_timeout(self): # Timeout is expected in 10ms. self.protocol.timeout = 10 * MS # Check the timing within -1/+9ms for robustness. with self.assertCompletesWithin(9 * MS, 19 * MS): # Unlike previous tests, no close frame will be received in # response. The server will stop waiting for the close frame and # timeout. self.loop.run_until_complete(self.protocol.close(reason='close')) self.assertConnectionClosed(1006, '') class ClientTests(CommonTests, unittest.TestCase): def setUp(self): super().setUp() self.protocol.is_client = True def test_close_handshake_timeout(self): # Timeout is expected in 2 * 10 = 20ms. self.protocol.timeout = 10 * MS # Check the timing within -1/+9ms for robustness. with self.assertCompletesWithin(19 * MS, 29 * MS): # Unlike previous tests, no close frame will be received in # response and the connection will not be closed. The client will # stop waiting for the close frame and timeout, then stop waiting # for the connection close and timeout again. self.loop.run_until_complete(self.protocol.close(reason='close')) self.assertConnectionClosed(1006, '') def test_eof_received_timeout(self): # Timeout is expected in 10ms. self.protocol.timeout = 10 * MS # Check the timing within -1/+9ms for robustness. with self.assertCompletesWithin(9 * MS, 19 * MS): # Unlike previous tests, the close frame will be received in # response but the connection will not be closed. The client will # stop waiting for the connection close and timeout. self.receive_frame(self.close_frame) self.loop.run_until_complete(self.protocol.close(reason='close')) self.assertConnectionClosed(1000, 'close') websockets-3.4/websockets/test_speedups.py0000644000076500000240000000000013146263121021053 0ustar mykstaff00000000000000websockets-3.4/websockets/test_uri.py0000644000076500000240000000164413146263121020041 0ustar mykstaff00000000000000import unittest from .exceptions import InvalidURI from .uri import * VALID_URIS = [ ('ws://localhost/', (False, 'localhost', 80, '/')), ('wss://localhost/', (True, 'localhost', 443, '/')), ('ws://localhost/path?query', (False, 'localhost', 80, '/path?query')), ('WS://LOCALHOST/PATH?QUERY', (False, 'localhost', 80, '/PATH?QUERY')), ] INVALID_URIS = [ 'http://localhost/', 'https://localhost/', 'ws://localhost/path#fragment', 'ws://user:pass@localhost/', ] class URITests(unittest.TestCase): def test_success(self): for uri, parsed in VALID_URIS: # wrap in `with self.subTest():` when dropping Python 3.3 self.assertEqual(parse_uri(uri), parsed) def test_error(self): for uri in INVALID_URIS: # wrap in `with self.subTest():` when dropping Python 3.3 with self.assertRaises(InvalidURI): parse_uri(uri) websockets-3.4/websockets/test_utils.py0000644000076500000240000000264113146263121020400 0ustar mykstaff00000000000000import unittest from .utils import apply_mask as py_apply_mask class UtilsTests(unittest.TestCase): @staticmethod def apply_mask(*args, **kwargs): return py_apply_mask(*args, **kwargs) def test_apply_mask(self): for data_in, mask, data_out in [ (b'', b'1234', b''), (b'aBcDe', b'\x00\x00\x00\x00', b'aBcDe'), (b'abcdABCD', b'1234', b'PPPPpppp'), (b'abcdABCD' * 10, b'1234', b'PPPPpppp' * 10), ]: self.assertEqual(self.apply_mask(data_in, mask), data_out) def test_apply_mask_check_input_types(self): for data_in, mask in [ (None, None), (b'abcd', None), (None, b'abcd'), ]: with self.assertRaises(TypeError): self.apply_mask(data_in, mask) def test_apply_mask_check_mask_length(self): for data_in, mask in [ (b'', b''), (b'abcd', b'123'), (b'', b'aBcDe'), (b'12345678', b'12345678'), ]: with self.assertRaises(ValueError): self.apply_mask(data_in, mask) try: from .speedups import apply_mask as c_apply_mask except ImportError: # pragma: no cover pass else: class SpeedupsTests(UtilsTests): @staticmethod def apply_mask(*args, **kwargs): return c_apply_mask(*args, **kwargs) websockets-3.4/websockets/uri.py0000644000076500000240000000275513146263121017006 0ustar mykstaff00000000000000""" The :mod:`websockets.uri` module implements parsing of WebSocket URIs according to `section 3 of RFC 6455`_. .. _section 3 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-3 """ import collections import urllib.parse from .exceptions import InvalidURI __all__ = ['parse_uri', 'WebSocketURI'] WebSocketURI = collections.namedtuple( 'WebSocketURI', ('secure', 'host', 'port', 'resource_name')) WebSocketURI.__doc__ = """WebSocket URI. * ``secure`` is the secure flag * ``host`` is the lower-case host * ``port`` if the integer port, it's always provided even if it's the default * ``resource_name`` is the resource name, that is, the path and optional query """ def parse_uri(uri): """ This function parses and validates a WebSocket URI. If the URI is valid, it returns a :class:`WebSocketURI`. Otherwise it raises an :exc:`~websockets.exceptions.InvalidURI` exception. """ uri = urllib.parse.urlparse(uri) try: assert uri.scheme in ('ws', 'wss') assert uri.params == '' assert uri.fragment == '' assert uri.username is None assert uri.password is None assert uri.hostname is not None except AssertionError as exc: raise InvalidURI() from exc secure = uri.scheme == 'wss' host = uri.hostname port = uri.port or (443 if secure else 80) resource_name = uri.path or '/' if uri.query: resource_name += '?' + uri.query return WebSocketURI(secure, host, port, resource_name) websockets-3.4/websockets/utils.py0000644000076500000240000000042513146263121017337 0ustar mykstaff00000000000000import itertools __all__ = ['apply_mask'] def apply_mask(data, mask): """ Apply masking to websocket message. """ if len(mask) != 4: raise ValueError("mask must contain 4 bytes") return bytes(b ^ m for b, m in zip(data, itertools.cycle(mask))) websockets-3.4/websockets/version.py0000644000076500000240000000002013146274441017662 0ustar mykstaff00000000000000version = '3.4' websockets-3.4/websockets.egg-info/0000755000076500000240000000000013146274747017336 5ustar mykstaff00000000000000websockets-3.4/websockets.egg-info/dependency_links.txt0000644000076500000240000000000113146274747023404 0ustar mykstaff00000000000000 websockets-3.4/websockets.egg-info/PKG-INFO0000644000076500000240000000436613146274747020444 0ustar mykstaff00000000000000Metadata-Version: 1.1 Name: websockets Version: 3.4 Summary: An implementation of the WebSocket Protocol (RFC 6455) Home-page: https://github.com/aaugustin/websockets Author: Aymeric Augustin Author-email: aymeric.augustin@m4x.org License: BSD Description: WebSockets ========== ``websockets`` is a library for developing WebSocket servers_ and clients_ in Python. It implements `RFC 6455`_ with a focus on correctness and simplicity. It passes the `Autobahn Testsuite`_. Built on top of Python's asynchronous I/O support introduced in `PEP 3156`_, it provides an API based on coroutines, making it easy to write highly concurrent applications. Installation is as simple as ``pip install websockets``. It requires Python ≥ 3.4 or Python 3.3 with the ``asyncio`` module, which is available with ``pip install asyncio``. Documentation is available on `Read the Docs`_. Bug reports, patches and suggestions welcome! Just open an issue_ or send a `pull request`_. .. _servers: https://github.com/aaugustin/websockets/blob/master/example/server.py .. _clients: https://github.com/aaugustin/websockets/blob/master/example/client.py .. _RFC 6455: http://tools.ietf.org/html/rfc6455 .. _Autobahn Testsuite: https://github.com/aaugustin/websockets/blob/master/compliance/README.rst .. _PEP 3156: http://www.python.org/dev/peps/pep-3156/ .. _Read the Docs: https://websockets.readthedocs.io/ .. _issue: https://github.com/aaugustin/websockets/issues/new .. _pull request: https://github.com/aaugustin/websockets/compare/ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 websockets-3.4/websockets.egg-info/requires.txt0000644000076500000240000000004213146274747021732 0ustar mykstaff00000000000000 [:python_version=="3.3"] asyncio websockets-3.4/websockets.egg-info/SOURCES.txt0000644000076500000240000000251613146274747021226 0ustar mykstaff00000000000000LICENSE MANIFEST.in README.rst setup.cfg setup.py websockets/__init__.py websockets/client.py websockets/compatibility.py websockets/exceptions.py websockets/framing.py websockets/handshake.py websockets/http.py websockets/protocol.py websockets/server.py websockets/speedups.c websockets/test_client_server.py websockets/test_framing.py websockets/test_handshake.py websockets/test_http.py websockets/test_protocol.py websockets/test_speedups.py websockets/test_uri.py websockets/test_utils.py websockets/uri.py websockets/utils.py websockets/version.py websockets.egg-info/PKG-INFO websockets.egg-info/SOURCES.txt websockets.egg-info/dependency_links.txt websockets.egg-info/requires.txt websockets.egg-info/top_level.txt websockets/py35/__init__.py websockets/py35/client.py websockets/py35/client_server.py websockets/py35/server.py websockets/py35/__pycache__/__init__.cpython-33.pyc websockets/py35/__pycache__/__init__.cpython-34.pyc websockets/py35/__pycache__/__init__.cpython-35.pyc websockets/py35/__pycache__/__init__.cpython-36.pyc websockets/py35/__pycache__/client.cpython-35.pyc websockets/py35/__pycache__/client.cpython-36.pyc websockets/py35/__pycache__/client_server.cpython-35.pyc websockets/py35/__pycache__/client_server.cpython-36.pyc websockets/py35/__pycache__/server.cpython-35.pyc websockets/py35/__pycache__/server.cpython-36.pycwebsockets-3.4/websockets.egg-info/top_level.txt0000644000076500000240000000003313146274747022064 0ustar mykstaff00000000000000websockets websockets/py35