websockets-3.0/0000755000076500000240000000000012637222015013450 5ustar mykstaff00000000000000websockets-3.0/LICENSE0000644000076500000240000000300012555415645014462 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.0/MANIFEST.in0000644000076500000240000000001712637220432015205 0ustar mykstaff00000000000000include LICENSEwebsockets-3.0/PKG-INFO0000644000076500000240000000422612637222015014551 0ustar mykstaff00000000000000Metadata-Version: 1.1 Name: websockets Version: 3.0 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 Download-URL: https://pypi.python.org/pypi/websockets Description: ``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.org/ .. _issue: https://github.com/aaugustin/websockets/issues/new .. _pull request: https://github.com/aaugustin/websockets/compare/ Platform: all 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 websockets-3.0/README0000644000076500000240000000235712615162175014345 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.org/ .. _issue: https://github.com/aaugustin/websockets/issues/new .. _pull request: https://github.com/aaugustin/websockets/compare/ websockets-3.0/setup.cfg0000644000076500000240000000027312637222015015273 0ustar mykstaff00000000000000[bdist_wheel] python-tag = py33.py34 [flake8] ignore = F403 [isort] known_standard_library = asyncio lines_after_imports = 2 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 websockets-3.0/setup.py0000644000076500000240000000324512633341271015170 0ustar mykstaff00000000000000import os import sys import setuptools # Avoid polluting the .tar.gz with ._* files under Mac OS X os.putenv('COPYFILE_DISABLE', 'true') root = os.path.dirname(__file__) # Prevent distutils from complaining that a standard file wasn't found README = os.path.join(root, 'README') if not os.path.exists(README): os.symlink(README + '.rst', README) description = "An implementation of the WebSocket Protocol (RFC 6455)" with open(os.path.join(root, 'README'), encoding='utf-8') as f: long_description = '\n\n'.join(f.read().split('\n\n')[1:]) with open(os.path.join(root, 'websockets', 'version.py'), 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.") setuptools.setup( name='websockets', version=version, author='Aymeric Augustin', author_email='aymeric.augustin@m4x.org', url='https://github.com/aaugustin/websockets', description=description, long_description=long_description, download_url='https://pypi.python.org/pypi/websockets', packages=[ 'websockets', ], extras_require={ ':python_version=="3.3"': ['asyncio'], }, 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", ], platforms='all', license='BSD' ) websockets-3.0/websockets/0000755000076500000240000000000012637222015015621 5ustar mykstaff00000000000000websockets-3.0/websockets/__init__.py0000644000076500000240000000057712634607160017750 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 * __all__ = ( client.__all__ + exceptions.__all__ + protocol.__all__ + server.__all__ + uri.__all__ ) from .version import version as __version__ # noqa websockets-3.0/websockets/client.py0000644000076500000240000001402212637220434017453 0ustar mykstaff00000000000000""" The :mod:`websockets.client` module defines a simple WebSocket client API. """ __all__ = ['connect', 'WebSocketClientProtocol'] import asyncio import collections.abc import email.message from .exceptions import InvalidHandshake from .handshake import build_request, check_response from .http import USER_AGENT, read_response from .protocol import CONNECTING, OPEN, WebSocketCommonProtocol from .uri import parse_uri 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 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) self.request_headers = email.message.Message() for name, value in headers: self.request_headers[name] = value self.raw_request_headers = headers # Send handshake request. Since the URI and the headers only contain # ASCII characters, we can keep this simple. request = ['GET %s HTTP/1.1' % wsuri.resource_name] request.extend('{}: {}'.format(k, v) for k, v in headers) request.append('\r\n') request = '\r\n'.join(request).encode() self.writer.write(request) # Read handshake response. try: status_code, headers = yield from read_response(self.reader) except Exception as exc: raise InvalidHandshake("Malformed HTTP message") from exc if status_code != 101: raise InvalidHandshake("Bad status code: {}".format(status_code)) self.response_headers = headers self.raw_response_headers = list(headers.raw_items()) get_header = lambda k: headers.get(k, '') check_response(get_header, key) self.subprotocol = headers.get('Sec-WebSocket-Protocol', None) if (self.subprotocol is not None and self.subprotocol not in subprotocols): raise InvalidHandshake( "Unknown subprotocol: {}".format(self.subprotocol)) assert self.state == CONNECTING self.state = OPEN self.opening_handshake.set_result(True) @asyncio.coroutine def connect(uri, *, loop=None, klass=WebSocketClientProtocol, legacy_recv=False, origin=None, subprotocols=None, extra_headers=None, **kwds): """ This coroutine connects to a WebSocket server. It's a wrapper around the event loop's :meth:`~asyncio.BaseEventLoop.create_connection` method. Extra 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. :func:`connect` accepts several 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` yields a :class:`WebSocketClientProtocol` which can then be used to send and receive messages. It raises :exc:`~websockets.uri.InvalidURI` if ``uri`` is invalid and :exc:`~websockets.handshake.InvalidHandshake` if the handshake fails. On Python 3.5, it 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() wsuri = parse_uri(uri) if wsuri.secure: kwds.setdefault('ssl', True) elif 'ssl' in kwds: raise ValueError("connect() received a SSL context for a ws:// URI. " "Use a wss:// URI to enable TLS.") factory = lambda: klass( host=wsuri.host, port=wsuri.port, secure=wsuri.secure, 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: # 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.0/websockets/compatibility.py0000644000076500000240000000055612633364451021061 0ustar mykstaff00000000000000import asyncio # 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 websockets-3.0/websockets/exceptions.py0000644000076500000240000000267012633364451020370 0ustar mykstaff00000000000000__all__ = [ 'InvalidHandshake', 'InvalidOrigin', 'InvalidState', 'InvalidURI', 'ConnectionClosed', 'PayloadTooBig', 'WebSocketProtocolError', ] class InvalidHandshake(Exception): """ Exception raised when a handshake request or response is invalid. """ class InvalidOrigin(InvalidHandshake): """ Exception raised when the origin in a handshake request is forbidden. """ 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.0/websockets/framing.py0000644000076500000240000001353612633364451017635 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 __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 = bytes(b ^ mask_bits[i % 4] for i, b in enumerate(data)) 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 = bytes(b ^ mask_bits[i % 4] for i, b in enumerate(frame.data)) 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.0/websockets/handshake.py0000644000076500000240000001032612633364451020132 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`. """ __all__ = [ 'build_request', 'check_request', 'build_response', 'check_response', ] import base64 import hashlib import random from .exceptions import InvalidHandshake 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.0/websockets/http.py0000644000076500000240000000551112633364451017163 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`. """ __all__ = ['read_request', 'read_response', 'USER_AGENT'] import asyncio import email.parser import io import sys from .version import version as websockets_version MAX_HEADERS = 256 MAX_LINE = 4096 USER_AGENT = ' '.join(( 'Python/{}'.format(sys.version[:3]), 'websockets/{}'.format(websockets_version), )) @asyncio.coroutine def read_request(stream): """ Read an HTTP/1.1 request from ``stream``. Return ``(path, headers)`` where ``path`` is a :class:`str` and ``headers`` is a :class:`~email.message.Message`. ``path`` isn't URL-decoded. Raise an exception if the request isn't well formatted. The request is assumed not to contain a body. """ request_line, headers = yield from read_message(stream) method, path, version = request_line[:-2].decode().split(None, 2) if method != 'GET': raise ValueError("Unsupported method") if version != 'HTTP/1.1': raise ValueError("Unsupported HTTP version") return path, headers @asyncio.coroutine def read_response(stream): """ Read an HTTP/1.1 response from ``stream``. Return ``(status, headers)`` where ``status`` is a :class:`int` and ``headers`` is a :class:`~email.message.Message`. Raise an exception if the request isn't well formatted. The response is assumed not to contain a body. """ status_line, headers = yield from read_message(stream) version, status, reason = status_line[:-2].decode().split(None, 2) if version != 'HTTP/1.1': raise ValueError("Unsupported HTTP version") return int(status), headers @asyncio.coroutine def read_message(stream): """ Read an HTTP message from ``stream``. Return ``(start_line, headers)`` where ``start_line`` is :class:`bytes` and ``headers`` is a :class:`~email.message.Message`. The message is assumed not to contain a body. """ start_line = yield from read_line(stream) header_lines = io.BytesIO() for num in range(MAX_HEADERS): header_line = yield from read_line(stream) header_lines.write(header_line) if header_line == b'\r\n': break else: raise ValueError("Too many headers") header_lines.seek(0) headers = email.parser.BytesHeaderParser().parse(header_lines) return start_line, headers @asyncio.coroutine def read_line(stream): """ Read a single line from ``stream``. """ line = yield from stream.readline() if len(line) > MAX_LINE: raise ValueError("Line too long") if not line.endswith(b'\r\n'): raise ValueError("Line without CRLF") return line websockets-3.0/websockets/protocol.py0000644000076500000240000005664512633476057020071 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 """ __all__ = ['WebSocketCommonProtocol'] 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 * 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. Once the handshake is complete, request and response HTTP headers are available: * as a MIME :class:`~email.message.Message` 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 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, loop=None, legacy_recv=False): self.host = host self.port = port self.secure = secure self.timeout = timeout self.max_size = max_size # Store a reference to loop to avoid relying on self._loop, a private # attribute of StreamReaderProtocol, inherited from FlowControlMixin. self.loop = loop self.legacy_recv = legacy_recv stream_reader = asyncio.StreamReader(loop=loop) super().__init__(stream_reader, self.client_connected, loop) self.reader = None self.writer = 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(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 = 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, self.timeout, loop=self.loop) except asyncio.TimeoutError: self.worker.cancel() # The worker should terminate quickly once it has been cancelled. yield from self.worker @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], 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, 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 self.messages.put_nowait(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: # 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 # Start the task that handles incoming messages. self.worker = 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.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.0/websockets/py35_client.py0000644000076500000240000000107012637220434020332 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.0/websockets/py35_test_client_server.py0000644000076500000240000000167412637220434022771 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_basic(self): server = serve(handler, 'localhost', 8642) self.server = self.loop.run_until_complete(server) async def basic(): 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(basic()) self.server.close() self.loop.run_until_complete(self.server.wait_closed()) websockets-3.0/websockets/server.py0000644000076500000240000002665112633364451017522 0ustar mykstaff00000000000000""" The :mod:`websockets.server` module defines a simple WebSocket server API. """ __all__ = ['serve', 'WebSocketServerProtocol'] import asyncio import collections.abc import email.message import logging from .compatibility import asyncio_ensure_future from .exceptions import InvalidHandshake, InvalidOrigin from .handshake import build_response, check_request from .http import USER_AGENT, read_request from .protocol import CONNECTING, OPEN, WebSocketCommonProtocol 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 Exception as exc: logger.info("Exception in opening handshake: {}".format(exc)) if isinstance(exc, InvalidOrigin): response = 'HTTP/1.1 403 Forbidden\r\n\r\n' + str(exc) elif isinstance(exc, InvalidHandshake): response = 'HTTP/1.1 400 Bad Request\r\n\r\n' + str(exc) else: response = ('HTTP/1.1 500 Internal Server Error\r\n\r\n' 'See server log for more information.') self.writer.write(response.encode()) raise try: yield from self.ws_handler(self, path) except Exception: logger.error("Exception in connection handler", exc_info=True) yield from self.fail_connection(1011) raise try: yield from self.close() except Exception as exc: logger.info("Exception in closing handshake: {}".format(exc)) 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) @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. Return the URI of the request. """ # Read handshake request. try: path, headers = yield from read_request(self.reader) except Exception as exc: raise InvalidHandshake("Malformed HTTP message") from exc self.request_headers = headers self.raw_request_headers = list(headers.raw_items()) get_header = lambda k: headers.get(k, '') key = check_request(get_header) if origins is not None: origin = get_header('Origin') if not set(origin.split() or ['']) <= set(origins): raise InvalidOrigin("Origin not allowed: {}".format(origin)) if subprotocols is not None: protocol = get_header('Sec-WebSocket-Protocol') if protocol: client_subprotocols = [p.strip() for p in protocol.split(',')] self.subprotocol = self.select_subprotocol( client_subprotocols, subprotocols) headers = [] set_header = lambda k, v: 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) self.response_headers = email.message.Message() for name, value in headers: self.response_headers[name] = value self.raw_response_headers = headers # Send handshake response. Since the status line and headers only # contain ASCII characters, we can keep this simple. response = ['HTTP/1.1 101 Switching Protocols'] response.extend('{}: {}'.format(k, v) for k, v in headers) response.append('\r\n') response = '\r\n'.join(response).encode() self.writer.write(response) assert self.state == CONNECTING self.state = OPEN self.opening_handshake.set_result(True) return path def select_subprotocol(self, 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] class WebSocketServer(asyncio.AbstractServer): """ Wrapper for :class:`~asyncio.Server` that triggers the closing handshake. """ def __init__(self, loop=None): # Store a reference to loop to avoid relying on self.server._loop. self.loop = loop self.websockets = set() def wrap(self, server): """ Attach to a given :class:`~asyncio.Server`. Since :meth:`~asyncio.BaseEventLoop.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.BaseEventLoop.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): """ Stop serving and trigger a closing handshake on open connections. """ for websocket in self.websockets: asyncio_ensure_future(websocket.close(1001), loop=self.loop) self.server.close() @asyncio.coroutine def wait_closed(self): """ Wait until all connections are closed. """ # asyncio.wait doesn't accept an empty first argument. if self.websockets: yield from asyncio.wait( [ws.handler_task for ws in self.websockets], loop=self.loop) yield from self.server.wait_closed() @asyncio.coroutine def serve(ws_handler, host=None, port=None, *, loop=None, klass=WebSocketServerProtocol, legacy_recv=False, origins=None, subprotocols=None, extra_headers=None, **kwds): """ This coroutine creates a WebSocket server. It's a wrapper around the event loop's :meth:`~asyncio.BaseEventLoop.create_server` method. ``host``, ``port`` as well as extra keyword arguments are passed to :meth:`~asyncio.BaseEventLoop.create_server`. For example, you can set the ``ssl`` keyword argument to a :class:`~ssl.SSLContext` to enable TLS. ``ws_handler`` is the WebSocket handler. It must be a coroutine accepting two arguments: a :class:`WebSocketServerProtocol` and the request URI. :func:`serve` accepts several 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. :func:`serve` yields a :class:`~asyncio.Server` which provides: * a :meth:`~asyncio.Server.close` method that closes open connections with status code 1001 and stops accepting new connections * a :meth:`~asyncio.Server.wait_closed` coroutine that waits until closing handshakes complete and connections are closed. 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() ws_server = WebSocketServer() secure = kwds.get('ssl') is not None factory = lambda: klass( ws_handler, ws_server, host=host, port=port, secure=secure, origins=origins, subprotocols=subprotocols, extra_headers=extra_headers, loop=loop, legacy_recv=legacy_recv) server = yield from loop.create_server(factory, host, port, **kwds) ws_server.wrap(server) return ws_server websockets-3.0/websockets/test_client_server.py0000644000076500000240000003711512637220434022110 0ustar mykstaff00000000000000import asyncio import logging import os import ssl import unittest import unittest.mock from .client import * from .exceptions import ConnectionClosed, InvalidHandshake 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 == '/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())) 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): self.loop.run_until_complete(self.client.worker) def stop_server(self): self.server.close() self.loop.run_until_complete(self.server.wait_closed()) def test_basic(self): self.start_server() self.start_client() self.loop.run_until_complete(self.client.send("Hello!")) reply = self.loop.run_until_complete(self.client.recv()) self.assertEqual(reply, "Hello!") self.stop_client() self.stop_server() def test_server_close_while_client_connected(self): self.start_server() self.start_client() self.stop_server() def test_explicit_event_loop(self): self.start_server(loop=self.loop) self.start_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!") self.stop_client() self.stop_server() def test_protocol_attributes(self): self.start_server() self.start_client('attributes') 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)) self.stop_client() self.stop_server() def test_protocol_headers(self): self.start_server() self.start_client('headers') 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)) self.stop_client() self.stop_server() def test_protocol_raw_headers(self): self.start_server() self.start_client('raw_headers') 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)) self.stop_client() self.stop_server() def test_protocol_custom_request_headers_dict(self): self.start_server() self.start_client('raw_headers', extra_headers={'X-Spam': 'Eggs'}) 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) self.stop_client() self.stop_server() def test_protocol_custom_request_headers_list(self): self.start_server() self.start_client('raw_headers', extra_headers=[('X-Spam', 'Eggs')]) 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) self.stop_client() self.stop_server() def test_protocol_custom_response_headers_callable_dict(self): self.start_server(extra_headers=lambda p, r: {'X-Spam': 'Eggs'}) self.start_client('raw_headers') 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) self.stop_client() self.stop_server() def test_protocol_custom_response_headers_callable_list(self): self.start_server(extra_headers=lambda p, r: [('X-Spam', 'Eggs')]) self.start_client('raw_headers') 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) self.stop_client() self.stop_server() def test_protocol_custom_response_headers_dict(self): self.start_server(extra_headers={'X-Spam': 'Eggs'}) self.start_client('raw_headers') 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) self.stop_client() self.stop_server() def test_protocol_custom_response_headers_list(self): self.start_server(extra_headers=[('X-Spam', 'Eggs')]) self.start_client('raw_headers') 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) self.stop_client() self.stop_server() def test_no_subprotocol(self): self.start_server() self.start_client('subprotocol') server_subprotocol = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_subprotocol, repr(None)) self.assertEqual(self.client.subprotocol, None) self.stop_client() self.stop_server() def test_subprotocol_found(self): self.start_server(subprotocols=['superchat', 'chat']) self.start_client('subprotocol', subprotocols=['otherchat', 'chat']) server_subprotocol = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_subprotocol, repr('chat')) self.assertEqual(self.client.subprotocol, 'chat') self.stop_client() self.stop_server() def test_subprotocol_not_found(self): self.start_server(subprotocols=['superchat']) self.start_client('subprotocol', subprotocols=['otherchat']) server_subprotocol = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_subprotocol, repr(None)) self.assertEqual(self.client.subprotocol, None) self.stop_client() self.stop_server() def test_subprotocol_not_offered(self): self.start_server() self.start_client('subprotocol', subprotocols=['otherchat', 'chat']) server_subprotocol = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_subprotocol, repr(None)) self.assertEqual(self.client.subprotocol, None) self.stop_client() self.stop_server() def test_subprotocol_not_requested(self): self.start_server(subprotocols=['superchat', 'chat']) self.start_client('subprotocol') server_subprotocol = self.loop.run_until_complete(self.client.recv()) self.assertEqual(server_subprotocol, repr(None)) self.assertEqual(self.client.subprotocol, None) self.stop_client() self.stop_server() @unittest.mock.patch.object( WebSocketServerProtocol, 'select_subprotocol', autospec=True) def test_subprotocol_error(self, _select_subprotocol): _select_subprotocol.return_value = 'superchat' self.start_server(subprotocols=['superchat']) with self.assertRaises(InvalidHandshake): self.start_client('subprotocol', subprotocols=['otherchat']) self.run_loop_once() self.stop_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") self.start_server() with self.assertRaises(InvalidHandshake): self.start_client() self.stop_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") self.start_server() with self.assertRaises(InvalidHandshake): self.start_client() self.run_loop_once() self.stop_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 self.start_server() with self.assertRaises(InvalidHandshake): self.start_client() self.stop_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 self.start_server() with self.assertRaises(InvalidHandshake): self.start_client() self.stop_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): code, headers = yield from read_response(stream) return 400, headers _read_response.side_effect = wrong_read_response self.start_server() with self.assertRaises(InvalidHandshake): self.start_client() self.run_loop_once() self.stop_server() @unittest.mock.patch('websockets.server.WebSocketServerProtocol.send') def test_server_handler_crashes(self, send): send.side_effect = ValueError("send failed") self.start_server() self.start_client() self.loop.run_until_complete(self.client.send("Hello!")) with self.assertRaises(ConnectionClosed): self.loop.run_until_complete(self.client.recv()) self.stop_client() self.stop_server() # Connection ends with an unexpected error. self.assertEqual(self.client.close_code, 1011) @unittest.mock.patch('websockets.server.WebSocketServerProtocol.close') def test_server_close_crashes(self, close): close.side_effect = ValueError("close failed") self.start_server() self.start_client() self.loop.run_until_complete(self.client.send("Hello!")) reply = self.loop.run_until_complete(self.client.recv()) self.assertEqual(reply, "Hello!") self.stop_client() self.stop_server() # 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) def test_ws_uri_is_rejected(self): self.start_server() client = connect('ws://localhost:8642/', ssl=self.client_context) with self.assertRaises(ValueError): self.loop.run_until_complete(client) self.stop_server() 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, "Bad status code: 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_test_client_server import ClientServerContextManager except SyntaxError: # pragma: no cover pass else: class ClientServerContextManagerTests(ClientServerContextManager, unittest.TestCase): pass websockets-3.0/websockets/test_framing.py0000644000076500000240000001237312633364451020672 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.0/websockets/test_handshake.py0000644000076500000240000001017712633364451021175 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.0/websockets/test_http.py0000644000076500000240000000546312633364451020230 0ustar mykstaff00000000000000import asyncio import unittest from .http import * from .http import read_message # private API class HTTPTests(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(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, hdrs = self.loop.run_until_complete(read_response(self.stream)) self.assertEqual(status, 101) self.assertEqual(hdrs['Upgrade'], 'websocket') def test_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_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)) 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_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_message(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_message(self.stream)) def test_line_ending(self): self.stream.feed_data(b'GET / HTTP/1.1\n\n') with self.assertRaises(ValueError): self.loop.run_until_complete(read_message(self.stream)) websockets-3.0/websockets/test_protocol.py0000644000076500000240000007344412634607160021114 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 async(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.async(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_once_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_once_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) self.assertConnectionClosed(1011, '') def test_recv_cancelled(self): recv = self.async(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.async, 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.async(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.async(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.0/websockets/test_uri.py0000644000076500000240000000164412633364451020045 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.0/websockets/uri.py0000644000076500000240000000275512633364451017012 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 """ __all__ = ['parse_uri', 'WebSocketURI'] import collections import urllib.parse from .exceptions import InvalidURI 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.0/websockets/version.py0000644000076500000240000000002012637221732017655 0ustar mykstaff00000000000000version = '3.0' websockets-3.0/websockets.egg-info/0000755000076500000240000000000012637222015017313 5ustar mykstaff00000000000000websockets-3.0/websockets.egg-info/dependency_links.txt0000644000076500000240000000000112637222014023360 0ustar mykstaff00000000000000 websockets-3.0/websockets.egg-info/PKG-INFO0000644000076500000240000000422612637222014020413 0ustar mykstaff00000000000000Metadata-Version: 1.1 Name: websockets Version: 3.0 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 Download-URL: https://pypi.python.org/pypi/websockets Description: ``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.org/ .. _issue: https://github.com/aaugustin/websockets/issues/new .. _pull request: https://github.com/aaugustin/websockets/compare/ Platform: all 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 websockets-3.0/websockets.egg-info/requires.txt0000644000076500000240000000004212637222014021706 0ustar mykstaff00000000000000 [:python_version=="3.3"] asyncio websockets-3.0/websockets.egg-info/SOURCES.txt0000644000076500000240000000126012637222015021176 0ustar mykstaff00000000000000LICENSE MANIFEST.in README 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/py35_client.py websockets/py35_test_client_server.py websockets/server.py websockets/test_client_server.py websockets/test_framing.py websockets/test_handshake.py websockets/test_http.py websockets/test_protocol.py websockets/test_uri.py websockets/uri.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.txtwebsockets-3.0/websockets.egg-info/top_level.txt0000644000076500000240000000001312637222014022036 0ustar mykstaff00000000000000websockets