pax_global_header00006660000000000000000000000064135122206460014513gustar00rootroot0000000000000052 comment=e9bdb4e92e398e5a527a59edd366552c332fc2d4 gevent-websocket-0.10.1/000077500000000000000000000000001351222064600150465ustar00rootroot00000000000000gevent-websocket-0.10.1/AUTHORS000066400000000000000000000002261351222064600161160ustar00rootroot00000000000000This Websocket library for Gevent is written and maintained by Jeffrey Gelens Contributors: Denis Bilenko Lon Ingram gevent-websocket-0.10.1/LICENSE000066400000000000000000000011401351222064600160470ustar00rootroot00000000000000 Copyright 2011-2017 Jeffrey Gelens Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gevent-websocket-0.10.1/MANIFEST.in000066400000000000000000000001071351222064600166020ustar00rootroot00000000000000include LICENSE include AUTHORS include README.rst include MANIFEST.in gevent-websocket-0.10.1/PKG-INFO000066400000000000000000000122451351222064600161470ustar00rootroot00000000000000Metadata-Version: 1.1 Name: gevent-websocket Version: 0.10.1 Summary: Websocket handler for the gevent pywsgi server, a Python network library Home-page: https://www.gitlab.com/noppo/gevent-websocket Author: Jeffrey Gelens Author-email: jeffrey@noppo.pro License: Copyright 2011-2017 Jeffrey Gelens Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Download-URL: https://www.gitlab.com/noppo/gevent-websocket Description: ================ gevent-websocket ================ `gevent-websocket`_ is a WebSocket library for the gevent_ networking library. Features include: - Integration on both socket level or using an abstract interface. - RPC and PubSub framework using `WAMP`_ (WebSocket Application Messaging Protocol). - Easily extendible using a simple WebSocket protocol plugin API :: from geventwebsocket import WebSocketServer, WebSocketApplication, Resource class EchoApplication(WebSocketApplication): def on_open(self): print "Connection opened" def on_message(self, message): self.ws.send(message) def on_close(self, reason): print reason WebSocketServer( ('', 8000), Resource({'/': EchoApplication}) ).serve_forever() or a low level implementation:: from gevent import pywsgi from geventwebsocket.handler import WebSocketHandler def websocket_app(environ, start_response): if environ["PATH_INFO"] == '/echo': ws = environ["wsgi.websocket"] message = ws.receive() ws.send(message) server = pywsgi.WSGIServer(("", 8000), websocket_app, handler_class=WebSocketHandler) server.serve_forever() More examples can be found in the ``examples`` directory. Hopefully more documentation will be available soon. Installation ------------ The easiest way to install gevent-websocket is directly from PyPi_ using pip or setuptools by running the commands below:: $ pip install gevent-websocket Gunicorn Worker ^^^^^^^^^^^^^^^ Using Gunicorn it is even more easy to start a server. Only the `websocket_app` from the previous example is required to start the server. Start Gunicorn using the following command and worker class to enable WebSocket funtionality for the application. :: gunicorn -k "geventwebsocket.gunicorn.workers.GeventWebSocketWorker" wsgi:websocket_app Performance ^^^^^^^^^^^ `gevent-websocket`_ is pretty fast, but can be accelerated further by installing `wsaccel `_ and `ujson` or `simplejson`:: $ pip install wsaccel ujson `gevent-websocket`_ automatically detects ``wsaccell`` and uses the Cython implementation for UTF8 validation and later also frame masking and demasking. Get in touch ^^^^^^^^^^^^ Get in touch on IRC #gevent on Freenode or on the Gevent `mailinglist `_. Issues can be created on `Bitbucket `_. .. _WAMP: http://www.wamp.ws .. _gevent-websocket: http://www.bitbucket.org/Jeffrey/gevent-websocket/ .. _gevent: http://www.gevent.org/ .. _Jeffrey Gelens: http://www.gelens.org/ .. _PyPi: http://pypi.python.org/pypi/gevent-websocket/ .. _repository: http://www.bitbucket.org/Jeffrey/gevent-websocket/ .. _RFC6455: http://datatracker.ietf.org/doc/rfc6455/?include_text=1 Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Internet Classifier: Topic :: Software Development :: Libraries :: Python Modules gevent-websocket-0.10.1/README.rst000066400000000000000000000056151351222064600165440ustar00rootroot00000000000000================ gevent-websocket ================ `gevent-websocket`_ is a WebSocket library for the gevent_ networking library. Features include: - Integration on both socket level or using an abstract interface. - RPC and PubSub framework using `WAMP`_ (WebSocket Application Messaging Protocol). - Easily extendible using a simple WebSocket protocol plugin API :: from geventwebsocket import WebSocketServer, WebSocketApplication, Resource class EchoApplication(WebSocketApplication): def on_open(self): print "Connection opened" def on_message(self, message): self.ws.send(message) def on_close(self, reason): print reason WebSocketServer( ('', 8000), Resource({'/': EchoApplication}) ).serve_forever() or a low level implementation:: from gevent import pywsgi from geventwebsocket.handler import WebSocketHandler def websocket_app(environ, start_response): if environ["PATH_INFO"] == '/echo': ws = environ["wsgi.websocket"] message = ws.receive() ws.send(message) server = pywsgi.WSGIServer(("", 8000), websocket_app, handler_class=WebSocketHandler) server.serve_forever() More examples can be found in the ``examples`` directory. Hopefully more documentation will be available soon. Installation ------------ The easiest way to install gevent-websocket is directly from PyPi_ using pip or setuptools by running the commands below:: $ pip install gevent-websocket Gunicorn Worker ^^^^^^^^^^^^^^^ Using Gunicorn it is even more easy to start a server. Only the `websocket_app` from the previous example is required to start the server. Start Gunicorn using the following command and worker class to enable WebSocket funtionality for the application. :: gunicorn -k "geventwebsocket.gunicorn.workers.GeventWebSocketWorker" wsgi:websocket_app Performance ^^^^^^^^^^^ `gevent-websocket`_ is pretty fast, but can be accelerated further by installing `wsaccel `_ and `ujson` or `simplejson`:: $ pip install wsaccel ujson `gevent-websocket`_ automatically detects ``wsaccell`` and uses the Cython implementation for UTF8 validation and later also frame masking and demasking. Get in touch ^^^^^^^^^^^^ Get in touch on IRC #gevent on Freenode or on the Gevent `mailinglist `_. Issues can be created on `Bitbucket `_. .. _WAMP: http://www.wamp.ws .. _gevent-websocket: http://www.bitbucket.org/Jeffrey/gevent-websocket/ .. _gevent: http://www.gevent.org/ .. _Jeffrey Gelens: http://www.gelens.org/ .. _PyPi: http://pypi.python.org/pypi/gevent-websocket/ .. _repository: http://www.bitbucket.org/Jeffrey/gevent-websocket/ .. _RFC6455: http://datatracker.ietf.org/doc/rfc6455/?include_text=1 gevent-websocket-0.10.1/gevent_websocket.egg-info/000077500000000000000000000000001351222064600220765ustar00rootroot00000000000000gevent-websocket-0.10.1/gevent_websocket.egg-info/PKG-INFO000066400000000000000000000122451351222064600231770ustar00rootroot00000000000000Metadata-Version: 1.1 Name: gevent-websocket Version: 0.10.1 Summary: Websocket handler for the gevent pywsgi server, a Python network library Home-page: https://www.gitlab.com/noppo/gevent-websocket Author: Jeffrey Gelens Author-email: jeffrey@noppo.pro License: Copyright 2011-2017 Jeffrey Gelens Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Download-URL: https://www.gitlab.com/noppo/gevent-websocket Description: ================ gevent-websocket ================ `gevent-websocket`_ is a WebSocket library for the gevent_ networking library. Features include: - Integration on both socket level or using an abstract interface. - RPC and PubSub framework using `WAMP`_ (WebSocket Application Messaging Protocol). - Easily extendible using a simple WebSocket protocol plugin API :: from geventwebsocket import WebSocketServer, WebSocketApplication, Resource class EchoApplication(WebSocketApplication): def on_open(self): print "Connection opened" def on_message(self, message): self.ws.send(message) def on_close(self, reason): print reason WebSocketServer( ('', 8000), Resource({'/': EchoApplication}) ).serve_forever() or a low level implementation:: from gevent import pywsgi from geventwebsocket.handler import WebSocketHandler def websocket_app(environ, start_response): if environ["PATH_INFO"] == '/echo': ws = environ["wsgi.websocket"] message = ws.receive() ws.send(message) server = pywsgi.WSGIServer(("", 8000), websocket_app, handler_class=WebSocketHandler) server.serve_forever() More examples can be found in the ``examples`` directory. Hopefully more documentation will be available soon. Installation ------------ The easiest way to install gevent-websocket is directly from PyPi_ using pip or setuptools by running the commands below:: $ pip install gevent-websocket Gunicorn Worker ^^^^^^^^^^^^^^^ Using Gunicorn it is even more easy to start a server. Only the `websocket_app` from the previous example is required to start the server. Start Gunicorn using the following command and worker class to enable WebSocket funtionality for the application. :: gunicorn -k "geventwebsocket.gunicorn.workers.GeventWebSocketWorker" wsgi:websocket_app Performance ^^^^^^^^^^^ `gevent-websocket`_ is pretty fast, but can be accelerated further by installing `wsaccel `_ and `ujson` or `simplejson`:: $ pip install wsaccel ujson `gevent-websocket`_ automatically detects ``wsaccell`` and uses the Cython implementation for UTF8 validation and later also frame masking and demasking. Get in touch ^^^^^^^^^^^^ Get in touch on IRC #gevent on Freenode or on the Gevent `mailinglist `_. Issues can be created on `Bitbucket `_. .. _WAMP: http://www.wamp.ws .. _gevent-websocket: http://www.bitbucket.org/Jeffrey/gevent-websocket/ .. _gevent: http://www.gevent.org/ .. _Jeffrey Gelens: http://www.gelens.org/ .. _PyPi: http://pypi.python.org/pypi/gevent-websocket/ .. _repository: http://www.bitbucket.org/Jeffrey/gevent-websocket/ .. _RFC6455: http://datatracker.ietf.org/doc/rfc6455/?include_text=1 Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Internet Classifier: Topic :: Software Development :: Libraries :: Python Modules gevent-websocket-0.10.1/gevent_websocket.egg-info/SOURCES.txt000066400000000000000000000013501351222064600237610ustar00rootroot00000000000000AUTHORS LICENSE MANIFEST.in README.rst setup.py gevent_websocket.egg-info/PKG-INFO gevent_websocket.egg-info/SOURCES.txt gevent_websocket.egg-info/dependency_links.txt gevent_websocket.egg-info/not-zip-safe gevent_websocket.egg-info/requires.txt gevent_websocket.egg-info/top_level.txt geventwebsocket/__init__.py geventwebsocket/_compat.py geventwebsocket/exceptions.py geventwebsocket/handler.py geventwebsocket/logging.py geventwebsocket/resource.py geventwebsocket/server.py geventwebsocket/utf8validator.py geventwebsocket/utils.py geventwebsocket/websocket.py geventwebsocket/gunicorn/__init__.py geventwebsocket/gunicorn/workers.py geventwebsocket/protocols/__init__.py geventwebsocket/protocols/base.py geventwebsocket/protocols/wamp.pygevent-websocket-0.10.1/gevent_websocket.egg-info/dependency_links.txt000066400000000000000000000000011351222064600261440ustar00rootroot00000000000000 gevent-websocket-0.10.1/gevent_websocket.egg-info/not-zip-safe000066400000000000000000000000011351222064600243240ustar00rootroot00000000000000 gevent-websocket-0.10.1/gevent_websocket.egg-info/requires.txt000066400000000000000000000000071351222064600244730ustar00rootroot00000000000000gevent gevent-websocket-0.10.1/gevent_websocket.egg-info/top_level.txt000066400000000000000000000000201351222064600246200ustar00rootroot00000000000000geventwebsocket gevent-websocket-0.10.1/geventwebsocket/000077500000000000000000000000001351222064600202455ustar00rootroot00000000000000gevent-websocket-0.10.1/geventwebsocket/__init__.py000066400000000000000000000006711351222064600223620ustar00rootroot00000000000000VERSION = (0, 10, 1, 'final', 0) __all__ = [ 'WebSocketApplication', 'Resource', 'WebSocketServer', 'WebSocketError', 'get_version' ] def get_version(*args, **kwargs): from .utils import get_version return get_version(*args, **kwargs) try: from .resource import WebSocketApplication, Resource from .server import WebSocketServer from .exceptions import WebSocketError except ImportError: pass gevent-websocket-0.10.1/geventwebsocket/_compat.py000066400000000000000000000007441351222064600222460ustar00rootroot00000000000000from __future__ import absolute_import, division, print_function import sys import codecs PY3 = sys.version_info[0] == 3 PY2 = sys.version_info[0] == 2 if PY2: bytes = str text_type = unicode string_types = basestring range_type = xrange iteritems = lambda x: x.iteritems() # b = lambda x: x else: text_type = str string_types = str, range_type = range iteritems = lambda x: iter(x.items()) # b = lambda x: codecs.latin_1_encode(x)[0] gevent-websocket-0.10.1/geventwebsocket/exceptions.py000066400000000000000000000005721351222064600230040ustar00rootroot00000000000000from socket import error as socket_error class WebSocketError(socket_error): """ Base class for all websocket errors. """ class ProtocolError(WebSocketError): """ Raised if an error occurs when de/encoding the websocket protocol. """ class FrameTooLargeException(ProtocolError): """ Raised if a frame is received that is too large. """ gevent-websocket-0.10.1/geventwebsocket/gunicorn/000077500000000000000000000000001351222064600220715ustar00rootroot00000000000000gevent-websocket-0.10.1/geventwebsocket/gunicorn/__init__.py000066400000000000000000000000001351222064600241700ustar00rootroot00000000000000gevent-websocket-0.10.1/geventwebsocket/gunicorn/workers.py000066400000000000000000000003041351222064600241340ustar00rootroot00000000000000from geventwebsocket.handler import WebSocketHandler from gunicorn.workers.ggevent import GeventPyWSGIWorker class GeventWebSocketWorker(GeventPyWSGIWorker): wsgi_handler = WebSocketHandler gevent-websocket-0.10.1/geventwebsocket/handler.py000066400000000000000000000225531351222064600222430ustar00rootroot00000000000000import base64 import hashlib from gevent.pywsgi import WSGIHandler from ._compat import PY3 from .websocket import WebSocket, Stream from .logging import create_logger class Client(object): def __init__(self, address, ws): self.address = address self.ws = ws class WebSocketHandler(WSGIHandler): """ Automatically upgrades the connection to a websocket. To prevent the WebSocketHandler to call the underlying WSGI application, but only setup the WebSocket negotiations, do: mywebsockethandler.prevent_wsgi_call = True before calling run_application(). This is useful if you want to do more things before calling the app, and want to off-load the WebSocket negotiations to this library. Socket.IO needs this for example, to send the 'ack' before yielding the control to your WSGI app. """ SUPPORTED_VERSIONS = ('13', '8', '7') GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" def run_websocket(self): """ Called when a websocket has been created successfully. """ if getattr(self, 'prevent_wsgi_call', False): return # In case WebSocketServer is not used if not hasattr(self.server, 'clients'): self.server.clients = {} # Since we're now a websocket connection, we don't care what the # application actually responds with for the http response try: self.server.clients[self.client_address] = Client( self.client_address, self.websocket) list(self.application(self.environ, lambda s, h, e=None: [])) finally: del self.server.clients[self.client_address] if not self.websocket.closed: self.websocket.close() self.environ.update({ 'wsgi.websocket': None }) self.websocket = None def run_application(self): if (hasattr(self.server, 'pre_start_hook') and self.server.pre_start_hook): self.logger.debug("Calling pre-start hook") if self.server.pre_start_hook(self): return super(WebSocketHandler, self).run_application() self.logger.debug("Initializing WebSocket") self.result = self.upgrade_websocket() if hasattr(self, 'websocket'): if self.status and not self.headers_sent: self.write('') self.run_websocket() else: if self.status: # A status was set, likely an error so just send the response if not self.result: self.result = [] self.process_result() return # This handler did not handle the request, so defer it to the # underlying application object return super(WebSocketHandler, self).run_application() def upgrade_websocket(self): """ Attempt to upgrade the current environ into a websocket enabled connection. If successful, the environ dict with be updated with two new entries, `wsgi.websocket` and `wsgi.websocket_version`. :returns: Whether the upgrade was successful. """ # Some basic sanity checks first self.logger.debug("Validating WebSocket request") if self.environ.get('REQUEST_METHOD', '') != 'GET': # This is not a websocket request, so we must not handle it self.logger.debug('Can only upgrade connection if using GET method.') return upgrade = self.environ.get('HTTP_UPGRADE', '').lower() if upgrade == 'websocket': connection = self.environ.get('HTTP_CONNECTION', '').lower() if 'upgrade' not in connection: # This is not a websocket request, so we must not handle it self.logger.warning("Client didn't ask for a connection " "upgrade") return else: # This is not a websocket request, so we must not handle it return if self.request_version != 'HTTP/1.1': self.start_response('402 Bad Request', []) self.logger.warning("Bad server protocol in headers") return ['Bad protocol version'] if self.environ.get('HTTP_SEC_WEBSOCKET_VERSION'): return self.upgrade_connection() else: self.logger.warning("No protocol defined") self.start_response('426 Upgrade Required', [ ('Sec-WebSocket-Version', ', '.join(self.SUPPORTED_VERSIONS))]) return ['No Websocket protocol version defined'] def upgrade_connection(self): """ Validate and 'upgrade' the HTTP request to a WebSocket request. If an upgrade succeeded then then handler will have `start_response` with a status of `101`, the environ will also be updated with `wsgi.websocket` and `wsgi.websocket_version` keys. :param environ: The WSGI environ dict. :param start_response: The callable used to start the response. :param stream: File like object that will be read from/written to by the underlying WebSocket object, if created. :return: The WSGI response iterator is something went awry. """ self.logger.debug("Attempting to upgrade connection") version = self.environ.get("HTTP_SEC_WEBSOCKET_VERSION") if version not in self.SUPPORTED_VERSIONS: msg = "Unsupported WebSocket Version: {0}".format(version) self.logger.warning(msg) self.start_response('400 Bad Request', [ ('Sec-WebSocket-Version', ', '.join(self.SUPPORTED_VERSIONS)) ]) return [msg] key = self.environ.get("HTTP_SEC_WEBSOCKET_KEY", '').strip() if not key: # 5.2.1 (3) msg = "Sec-WebSocket-Key header is missing/empty" self.logger.warning(msg) self.start_response('400 Bad Request', []) return [msg] try: key_len = len(base64.b64decode(key)) except TypeError: msg = "Invalid key: {0}".format(key) self.logger.warning(msg) self.start_response('400 Bad Request', []) return [msg] if key_len != 16: # 5.2.1 (3) msg = "Invalid key: {0}".format(key) self.logger.warning(msg) self.start_response('400 Bad Request', []) return [msg] # Check for WebSocket Protocols requested_protocols = self.environ.get( 'HTTP_SEC_WEBSOCKET_PROTOCOL', '') protocol = None if hasattr(self.application, 'app_protocol'): allowed_protocol = self.application.app_protocol( self.environ['PATH_INFO']) if allowed_protocol and allowed_protocol in requested_protocols: protocol = allowed_protocol self.logger.debug("Protocol allowed: {0}".format(protocol)) self.websocket = WebSocket(self.environ, Stream(self), self) self.environ.update({ 'wsgi.websocket_version': version, 'wsgi.websocket': self.websocket }) if PY3: accept = base64.b64encode( hashlib.sha1((key + self.GUID).encode("latin-1")).digest() ).decode("latin-1") else: accept = base64.b64encode(hashlib.sha1(key + self.GUID).digest()) headers = [ ("Upgrade", "websocket"), ("Connection", "Upgrade"), ("Sec-WebSocket-Accept", accept) ] if protocol: headers.append(("Sec-WebSocket-Protocol", protocol)) self.logger.debug("WebSocket request accepted, switching protocols") self.start_response("101 Switching Protocols", headers) @property def logger(self): if not hasattr(self.server, 'logger'): self.server.logger = create_logger(__name__) return self.server.logger def log_request(self): if '101' not in str(self.status): self.logger.info(self.format_request()) @property def active_client(self): return self.server.clients[self.client_address] def start_response(self, status, headers, exc_info=None): """ Called when the handler is ready to send a response back to the remote endpoint. A websocket connection may have not been created. """ writer = super(WebSocketHandler, self).start_response( status, headers, exc_info=exc_info) self._prepare_response() return writer def _prepare_response(self): """ Sets up the ``pywsgi.Handler`` to work with a websocket response. This is used by other projects that need to support WebSocket connections as part of a larger effort. """ assert not self.headers_sent if not self.environ.get('wsgi.websocket'): # a WebSocket connection is not established, do nothing return # So that `finalize_headers` doesn't write a Content-Length header self.provided_content_length = False # The websocket is now controlling the response self.response_use_chunked = False # Once the request is over, the connection must be closed self.close_connection = True # Prevents the Date header from being written self.provided_date = True gevent-websocket-0.10.1/geventwebsocket/logging.py000066400000000000000000000015531351222064600222510ustar00rootroot00000000000000from __future__ import absolute_import from logging import getLogger, StreamHandler, getLoggerClass, Formatter, DEBUG def create_logger(name, debug=False, format=None): Logger = getLoggerClass() class DebugLogger(Logger): def getEffectiveLevel(x): if x.level == 0 and debug: return DEBUG else: return Logger.getEffectiveLevel(x) class DebugHandler(StreamHandler): def emit(x, record): StreamHandler.emit(x, record) if debug else None handler = DebugHandler() handler.setLevel(DEBUG) if format: handler.setFormatter(Formatter(format)) logger = getLogger(name) del logger.handlers[:] logger.__class__ = DebugLogger logger.addHandler(handler) return logger gevent-websocket-0.10.1/geventwebsocket/protocols/000077500000000000000000000000001351222064600222715ustar00rootroot00000000000000gevent-websocket-0.10.1/geventwebsocket/protocols/__init__.py000066400000000000000000000000001351222064600243700ustar00rootroot00000000000000gevent-websocket-0.10.1/geventwebsocket/protocols/base.py000066400000000000000000000013401351222064600235530ustar00rootroot00000000000000class BaseProtocol(object): PROTOCOL_NAME = '' def __init__(self, app): self._app = app def on_open(self): self.app.on_open() def on_message(self, message): self.app.on_message(message) def on_close(self, reason=None): self.app.on_close(reason) @property def app(self): if self._app: return self._app else: raise Exception("No application coupled") @property def server(self): if not hasattr(self.app, 'ws'): return None return self.app.ws.handler.server @property def handler(self): if not hasattr(self.app, 'ws'): return None return self.app.ws.handler gevent-websocket-0.10.1/geventwebsocket/protocols/wamp.py000066400000000000000000000151311351222064600236100ustar00rootroot00000000000000import inspect import random import string import types try: import ujson as json except ImportError: try: import simplejson as json except ImportError: import json from .._compat import range_type, string_types from ..exceptions import WebSocketError from .base import BaseProtocol def export_rpc(arg=None): if isinstance(arg, types.FunctionType): arg._rpc = arg.__name__ return arg def serialize(data): return json.dumps(data) class Prefixes(object): def __init__(self): self.prefixes = {} def add(self, prefix, uri): self.prefixes[prefix] = uri def resolve(self, curie_or_uri): if "http://" in curie_or_uri: return curie_or_uri elif ':' in curie_or_uri: prefix, proc = curie_or_uri.split(':', 1) return self.prefixes[prefix] + proc else: raise Exception(curie_or_uri) class RemoteProcedures(object): def __init__(self): self.calls = {} def register_procedure(self, uri, proc): self.calls[uri] = proc def register_object(self, uri, obj): for k in inspect.getmembers(obj, inspect.ismethod): if '_rpc' in k[1].__dict__: proc_uri = uri + k[1]._rpc self.calls[proc_uri] = (obj, k[1]) def call(self, uri, args): if uri in self.calls: proc = self.calls[uri] # Do the correct call whether it's a function or instance method. if isinstance(proc, tuple): if proc[1].__self__ is None: # Create instance of object and call method return proc[1](proc[0](), *args) else: # Call bound method on instance return proc[1](*args) else: return self.calls[uri](*args) else: raise Exception("no such uri '{}'".format(uri)) class Channels(object): def __init__(self): self.channels = {} def create(self, uri, prefix_matching=False): if uri not in self.channels: self.channels[uri] = [] # TODO: implement prefix matching def subscribe(self, uri, client): if uri in self.channels: self.channels[uri].append(client) def unsubscribe(self, uri, client): if uri not in self.channels: return client_index = self.channels[uri].index(client) self.channels[uri].pop(client_index) if len(self.channels[uri]) == 0: del self.channels[uri] def publish(self, uri, event, exclude=None, eligible=None): if uri not in self.channels: return # TODO: exclude & eligible msg = [WampProtocol.MSG_EVENT, uri, event] for client in self.channels[uri]: try: client.ws.send(serialize(msg)) except WebSocketError: # Seems someone didn't unsubscribe before disconnecting self.channels[uri].remove(client) class WampProtocol(BaseProtocol): MSG_WELCOME = 0 MSG_PREFIX = 1 MSG_CALL = 2 MSG_CALL_RESULT = 3 MSG_CALL_ERROR = 4 MSG_SUBSCRIBE = 5 MSG_UNSUBSCRIBE = 6 MSG_PUBLISH = 7 MSG_EVENT = 8 PROTOCOL_NAME = "wamp" def __init__(self, *args, **kwargs): self.procedures = RemoteProcedures() self.prefixes = Prefixes() self.session_id = ''.join( [random.choice(string.digits + string.letters) for i in range_type(16)]) super(WampProtocol, self).__init__(*args, **kwargs) def register_procedure(self, *args, **kwargs): self.procedures.register_procedure(*args, **kwargs) def register_object(self, *args, **kwargs): self.procedures.register_object(*args, **kwargs) def register_pubsub(self, *args, **kwargs): if not hasattr(self.server, 'channels'): self.server.channels = Channels() self.server.channels.create(*args, **kwargs) def do_handshake(self): from geventwebsocket import get_version welcome = [ self.MSG_WELCOME, self.session_id, 1, 'gevent-websocket/' + get_version() ] self.app.ws.send(serialize(welcome)) def _get_exception_info(self, e): uri = 'http://TODO#generic' desc = str(type(e)) details = str(e) return [uri, desc, details] def rpc_call(self, data): call_id, curie_or_uri = data[1:3] args = data[3:] if not isinstance(call_id, string_types): raise Exception() if not isinstance(curie_or_uri, string_types): raise Exception() uri = self.prefixes.resolve(curie_or_uri) try: result = self.procedures.call(uri, args) result_msg = [self.MSG_CALL_RESULT, call_id, result] except Exception as e: result_msg = [self.MSG_CALL_ERROR, call_id] + self._get_exception_info(e) self.app.on_message(serialize(result_msg)) def pubsub_action(self, data): action = data[0] curie_or_uri = data[1] if not isinstance(action, int): raise Exception() if not isinstance(curie_or_uri, string_types): raise Exception() uri = self.prefixes.resolve(curie_or_uri) if action == self.MSG_SUBSCRIBE and len(data) == 2: self.server.channels.subscribe(data[1], self.handler.active_client) elif action == self.MSG_UNSUBSCRIBE and len(data) == 2: self.server.channels.unsubscribe( data[1], self.handler.active_client) elif action == self.MSG_PUBLISH and len(data) >= 3: payload = data[2] if len(data) >= 3 else None exclude = data[3] if len(data) >= 4 else None eligible = data[4] if len(data) >= 5 else None self.server.channels.publish(uri, payload, exclude, eligible) def on_open(self): self.app.on_open() self.do_handshake() def on_message(self, message): data = json.loads(message) if not isinstance(data, list): raise Exception('incoming data is no list') if data[0] == self.MSG_PREFIX and len(data) == 3: prefix, uri = data[1:3] self.prefixes.add(prefix, uri) elif data[0] == self.MSG_CALL and len(data) >= 3: return self.rpc_call(data) elif data[0] in (self.MSG_SUBSCRIBE, self.MSG_UNSUBSCRIBE, self.MSG_PUBLISH): return self.pubsub_action(data) else: raise Exception("Unknown call") gevent-websocket-0.10.1/geventwebsocket/resource.py000066400000000000000000000060051351222064600224470ustar00rootroot00000000000000import re import warnings from .protocols.base import BaseProtocol from .exceptions import WebSocketError try: from collections import OrderedDict except ImportError: class OrderedDict: pass class WebSocketApplication(object): protocol_class = BaseProtocol def __init__(self, ws): self.protocol = self.protocol_class(self) self.ws = ws def handle(self): self.protocol.on_open() while True: try: message = self.ws.receive() except WebSocketError: self.protocol.on_close() break self.protocol.on_message(message) def on_open(self, *args, **kwargs): pass def on_close(self, *args, **kwargs): pass def on_message(self, message, *args, **kwargs): self.ws.send(message, **kwargs) @classmethod def protocol_name(cls): return cls.protocol_class.PROTOCOL_NAME class Resource(object): def __init__(self, apps=None): self.apps = apps if apps else [] if isinstance(apps, dict): if not isinstance(apps, OrderedDict): warnings.warn("Using an unordered dictionary for the " "app list is discouraged and may lead to " "undefined behavior.", UserWarning) self.apps = apps.items() # An app can either be a standard WSGI application (an object we call with # __call__(self, environ, start_response)) or a class we instantiate # (and which can handle websockets). This function tells them apart. # Override this if you have apps that can handle websockets but don't # fulfill these criteria. def _is_websocket_app(self, app): return isinstance(app, type) and issubclass(app, WebSocketApplication) def _app_by_path(self, environ_path, is_websocket_request): # Which app matched the current path? for path, app in self.apps: if re.match(path, environ_path): if is_websocket_request == self._is_websocket_app(app): return app return None def app_protocol(self, path): # app_protocol will only be called for websocket apps app = self._app_by_path(path, True) if hasattr(app, 'protocol_name'): return app.protocol_name() else: return '' def __call__(self, environ, start_response): environ = environ is_websocket_call = 'wsgi.websocket' in environ current_app = self._app_by_path(environ['PATH_INFO'], is_websocket_call) if current_app is None: raise Exception("No apps defined") if is_websocket_call: ws = environ['wsgi.websocket'] current_app = current_app(ws) current_app.ws = ws # TODO: needed? current_app.handle() # Always return something, calling WSGI middleware may rely on it return [] else: return current_app(environ, start_response) gevent-websocket-0.10.1/geventwebsocket/server.py000066400000000000000000000016661351222064600221360ustar00rootroot00000000000000from gevent.pywsgi import WSGIServer from .handler import WebSocketHandler from .logging import create_logger class WebSocketServer(WSGIServer): handler_class = WebSocketHandler debug_log_format = ( '-' * 80 + '\n' + '%(levelname)s in %(module)s [%(pathname)s:%(lineno)d]:\n' + '%(message)s\n' + '-' * 80 ) def __init__(self, *args, **kwargs): self.debug = kwargs.pop('debug', False) self.pre_start_hook = kwargs.pop('pre_start_hook', None) self._logger = None self.clients = {} super(WebSocketServer, self).__init__(*args, **kwargs) def handle(self, socket, address): handler = self.handler_class(socket, address, self) handler.handle() @property def logger(self): if not self._logger: self._logger = create_logger( __name__, self.debug, self.debug_log_format) return self._logger gevent-websocket-0.10.1/geventwebsocket/utf8validator.py000066400000000000000000000235141351222064600234200ustar00rootroot00000000000000from ._compat import PY3 ############################################################################### # # The MIT License (MIT) # # Copyright (c) Crossbar.io Technologies GmbH # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # ############################################################################### # Note: This code is a Python implementation of the algorithm # "Flexible and Economical UTF-8 Decoder" by Bjoern Hoehrmann # bjoern@hoehrmann.de, http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ __all__ = ("Utf8Validator",) # DFA transitions UTF8VALIDATOR_DFA = ( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 00..1f 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 20..3f 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 40..5f 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 60..7f 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, # 80..9f 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, # a0..bf 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, # c0..df 0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, # e0..ef 0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, # f0..ff 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, # s0..s0 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, # s1..s2 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, # s3..s4 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, # s5..s6 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, # s7..s8 ) UTF8_ACCEPT = 0 UTF8_REJECT = 1 # use Cython implementation of UTF8 validator if available # try: from wsaccel.utf8validator import Utf8Validator except ImportError: # # Fallback to pure Python implementation - also for PyPy. # # Do NOT touch this code unless you know what you are doing! # https://github.com/oberstet/scratchbox/tree/master/python/utf8 # if PY3: # Python 3 and above # convert DFA table to bytes (performance) UTF8VALIDATOR_DFA_S = bytes(UTF8VALIDATOR_DFA) class Utf8Validator(object): """ Incremental UTF-8 validator with constant memory consumption (minimal state). Implements the algorithm "Flexible and Economical UTF-8 Decoder" by Bjoern Hoehrmann (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/). """ def __init__(self): self.reset() def decode(self, b): """ Eat one UTF-8 octet, and validate on the fly. Returns ``UTF8_ACCEPT`` when enough octets have been consumed, in which case ``self.codepoint`` contains the decoded Unicode code point. Returns ``UTF8_REJECT`` when invalid UTF-8 was encountered. Returns some other positive integer when more octets need to be eaten. """ tt = UTF8VALIDATOR_DFA_S[b] if self.state != UTF8_ACCEPT: self.codepoint = (b & 0x3f) | (self.codepoint << 6) else: self.codepoint = (0xff >> tt) & b self.state = UTF8VALIDATOR_DFA_S[256 + self.state * 16 + tt] return self.state def reset(self): """ Reset validator to start new incremental UTF-8 decode/validation. """ self.state = UTF8_ACCEPT # the empty string is valid UTF8 self.codepoint = 0 self.i = 0 def validate(self, ba): """ Incrementally validate a chunk of bytes provided as string. Will return a quad ``(valid?, endsOnCodePoint?, currentIndex, totalIndex)``. As soon as an octet is encountered which renders the octet sequence invalid, a quad with ``valid? == False`` is returned. ``currentIndex`` returns the index within the currently consumed chunk, and ``totalIndex`` the index within the total consumed sequence that was the point of bail out. When ``valid? == True``, currentIndex will be ``len(ba)`` and ``totalIndex`` the total amount of consumed bytes. """ # # The code here is written for optimal JITting in PyPy, not for best # readability by your grandma or particular elegance. Do NOT touch! # l = len(ba) i = 0 state = self.state while i < l: # optimized version of decode(), since we are not interested in actual code points state = UTF8VALIDATOR_DFA_S[256 + (state << 4) + UTF8VALIDATOR_DFA_S[ba[i]]] if state == UTF8_REJECT: self.state = state self.i += i return False, False, i, self.i i += 1 self.state = state self.i += l return True, state == UTF8_ACCEPT, l, self.i else: # convert DFA table to string (performance) UTF8VALIDATOR_DFA_S = ''.join([chr(c) for c in UTF8VALIDATOR_DFA]) class Utf8Validator(object): """ Incremental UTF-8 validator with constant memory consumption (minimal state). Implements the algorithm "Flexible and Economical UTF-8 Decoder" by Bjoern Hoehrmann (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/). """ def __init__(self): self.reset() def decode(self, b): """ Eat one UTF-8 octet, and validate on the fly. Returns ``UTF8_ACCEPT`` when enough octets have been consumed, in which case ``self.codepoint`` contains the decoded Unicode code point. Returns ``UTF8_REJECT`` when invalid UTF-8 was encountered. Returns some other positive integer when more octets need to be eaten. """ tt = ord(UTF8VALIDATOR_DFA_S[b]) if self.state != UTF8_ACCEPT: self.codepoint = (b & 0x3f) | (self.codepoint << 6) else: self.codepoint = (0xff >> tt) & b self.state = ord(UTF8VALIDATOR_DFA_S[256 + self.state * 16 + tt]) return self.state def reset(self): """ Reset validator to start new incremental UTF-8 decode/validation. """ self.state = UTF8_ACCEPT # the empty string is valid UTF8 self.codepoint = 0 self.i = 0 def validate(self, ba): """ Incrementally validate a chunk of bytes provided as string. Will return a quad ``(valid?, endsOnCodePoint?, currentIndex, totalIndex)``. As soon as an octet is encountered which renders the octet sequence invalid, a quad with ``valid? == False`` is returned. ``currentIndex`` returns the index within the currently consumed chunk, and ``totalIndex`` the index within the total consumed sequence that was the point of bail out. When ``valid? == True``, currentIndex will be ``len(ba)`` and ``totalIndex`` the total amount of consumed bytes. """ # # The code here is written for optimal JITting in PyPy, not for best # readability by your grandma or particular elegance. Do NOT touch! # l = len(ba) i = 0 state = self.state while i < l: # optimized version of decode(), since we are not interested in actual code points try: state = ord(UTF8VALIDATOR_DFA_S[256 + (state << 4) + ord(UTF8VALIDATOR_DFA_S[ba[i]])]) except: import ipdb; ipdb.set_trace() if state == UTF8_REJECT: self.state = state self.i += i return False, False, i, self.i i += 1 self.state = state self.i += l return True, state == UTF8_ACCEPT, l, self.i gevent-websocket-0.10.1/geventwebsocket/utils.py000066400000000000000000000022411351222064600217560ustar00rootroot00000000000000import subprocess def get_version(version=None): "Returns a PEP 386-compliant version number from VERSION." if version is None: from geventwebsocket import VERSION as version else: assert len(version) == 5 assert version[3] in ('alpha', 'beta', 'rc', 'final') # Now build the two parts of the version number: # main = X.Y[.Z] # sub = .devN - for pre-alpha releases # | {a|b|c}N - for alpha, beta and rc releases parts = 2 if version[2] == 0 else 3 main = '.'.join(str(x) for x in version[:parts]) sub = '' if version[3] == 'alpha' and version[4] == 0: hg_changeset = get_hg_changeset() if hg_changeset: sub = '.dev{0}'.format(hg_changeset) elif version[3] != 'final': mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'} sub = mapping[version[3]] + str(version[4]) return str(main + sub) def get_hg_changeset(): rev, err = subprocess.Popen( 'hg id -i', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ).communicate() if err: return None else: return rev.strip().replace('+', '') gevent-websocket-0.10.1/geventwebsocket/websocket.py000066400000000000000000000372561351222064600226220ustar00rootroot00000000000000import struct from socket import error from ._compat import string_types, range_type, text_type from .exceptions import ProtocolError from .exceptions import WebSocketError from .exceptions import FrameTooLargeException from .utf8validator import Utf8Validator MSG_SOCKET_DEAD = "Socket is dead" MSG_ALREADY_CLOSED = "Connection is already closed" MSG_CLOSED = "Connection closed" class WebSocket(object): """ Base class for supporting websocket operations. :ivar environ: The http environment referenced by this connection. :ivar closed: Whether this connection is closed/closing. :ivar stream: The underlying file like object that will be read from / written to by this WebSocket object. """ __slots__ = ('utf8validator', 'utf8validate_last', 'environ', 'closed', 'stream', 'raw_write', 'raw_read', 'handler') OPCODE_CONTINUATION = 0x00 OPCODE_TEXT = 0x01 OPCODE_BINARY = 0x02 OPCODE_CLOSE = 0x08 OPCODE_PING = 0x09 OPCODE_PONG = 0x0a def __init__(self, environ, stream, handler): self.environ = environ self.closed = False self.stream = stream self.raw_write = stream.write self.raw_read = stream.read self.utf8validator = Utf8Validator() self.handler = handler def __del__(self): try: self.close() except: # close() may fail if __init__ didn't complete pass def _decode_bytes(self, bytestring): """ Internal method used to convert the utf-8 encoded bytestring into unicode. If the conversion fails, the socket will be closed. """ if not bytestring: return '' try: return bytestring.decode('utf-8') except UnicodeDecodeError: self.close(1007) raise def _encode_bytes(self, text): """ :returns: The utf-8 byte string equivalent of `text`. """ if not isinstance(text, str): text = text_type(text or '') return text.encode("utf-8") def _is_valid_close_code(self, code): """ :returns: Whether the returned close code is a valid hybi return code. """ if code < 1000: return False if 1004 <= code <= 1006: return False if 1012 <= code <= 1016: return False if code == 1100: # not sure about this one but the autobahn fuzzer requires it. return False if 2000 <= code <= 2999: return False return True @property def current_app(self): if hasattr(self.handler.server.application, 'current_app'): return self.handler.server.application.current_app else: # For backwards compatibility reasons class MockApp(): def on_close(self, *args): pass return MockApp() @property def origin(self): if not self.environ: return return self.environ.get('HTTP_ORIGIN') @property def protocol(self): if not self.environ: return return self.environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL') @property def version(self): if not self.environ: return return self.environ.get('HTTP_SEC_WEBSOCKET_VERSION') @property def path(self): if not self.environ: return return self.environ.get('PATH_INFO') @property def logger(self): return self.handler.logger def handle_close(self, header, payload): """ Called when a close frame has been decoded from the stream. :param header: The decoded `Header`. :param payload: The bytestring payload associated with the close frame. """ if not payload: self.close(1000, None) return if len(payload) < 2: raise ProtocolError('Invalid close frame: {0} {1}'.format( header, payload)) code = struct.unpack('!H', payload[:2])[0] payload = payload[2:] if payload: validator = Utf8Validator() val = validator.validate(payload) if not val[0]: raise UnicodeError if not self._is_valid_close_code(code): raise ProtocolError('Invalid close code {0}'.format(code)) self.close(code, payload) def handle_ping(self, header, payload): self.send_frame(payload, self.OPCODE_PONG) def handle_pong(self, header, payload): pass def read_frame(self): """ Block until a full frame has been read from the socket. This is an internal method as calling this will not cleanup correctly if an exception is called. Use `receive` instead. :return: The header and payload as a tuple. """ header = Header.decode_header(self.stream) if header.flags: raise ProtocolError if not header.length: return header, b'' try: payload = self.raw_read(header.length) except error: payload = b'' except Exception: # TODO log out this exception payload = b'' if len(payload) != header.length: raise WebSocketError('Unexpected EOF reading frame payload') if header.mask: payload = header.unmask_payload(payload) return header, payload def validate_utf8(self, payload): # Make sure the frames are decodable independently self.utf8validate_last = self.utf8validator.validate(payload) if not self.utf8validate_last[0]: raise UnicodeError("Encountered invalid UTF-8 while processing " "text message at payload octet index " "{0:d}".format(self.utf8validate_last[3])) def read_message(self): """ Return the next text or binary message from the socket. This is an internal method as calling this will not cleanup correctly if an exception is called. Use `receive` instead. """ opcode = None message = bytearray() while True: header, payload = self.read_frame() f_opcode = header.opcode if f_opcode in (self.OPCODE_TEXT, self.OPCODE_BINARY): # a new frame if opcode: raise ProtocolError("The opcode in non-fin frame is " "expected to be zero, got " "{0!r}".format(f_opcode)) # Start reading a new message, reset the validator self.utf8validator.reset() self.utf8validate_last = (True, True, 0, 0) opcode = f_opcode elif f_opcode == self.OPCODE_CONTINUATION: if not opcode: raise ProtocolError("Unexpected frame with opcode=0") elif f_opcode == self.OPCODE_PING: self.handle_ping(header, payload) continue elif f_opcode == self.OPCODE_PONG: self.handle_pong(header, payload) continue elif f_opcode == self.OPCODE_CLOSE: self.handle_close(header, payload) return else: raise ProtocolError("Unexpected opcode={0!r}".format(f_opcode)) if opcode == self.OPCODE_TEXT: self.validate_utf8(payload) message += payload if header.fin: break if opcode == self.OPCODE_TEXT: self.validate_utf8(message) return self._decode_bytes(message) else: return message def receive(self): """ Read and return a message from the stream. If `None` is returned, then the socket is considered closed/errored. """ if self.closed: self.current_app.on_close(MSG_ALREADY_CLOSED) raise WebSocketError(MSG_ALREADY_CLOSED) try: return self.read_message() except UnicodeError: self.close(1007) except ProtocolError: self.close(1002) except error: self.close() self.current_app.on_close(MSG_CLOSED) return None def send_frame(self, message, opcode): """ Send a frame over the websocket with message as its payload """ if self.closed: self.current_app.on_close(MSG_ALREADY_CLOSED) raise WebSocketError(MSG_ALREADY_CLOSED) if opcode in (self.OPCODE_TEXT, self.OPCODE_PING): message = self._encode_bytes(message) elif opcode == self.OPCODE_BINARY: message = bytes(message) header = Header.encode_header(True, opcode, b'', len(message), 0) try: self.raw_write(header + message) except error: raise WebSocketError(MSG_SOCKET_DEAD) except: raise def send(self, message, binary=None): """ Send a frame over the websocket with message as its payload """ if binary is None: binary = not isinstance(message, string_types) opcode = self.OPCODE_BINARY if binary else self.OPCODE_TEXT try: self.send_frame(message, opcode) except WebSocketError: self.current_app.on_close(MSG_SOCKET_DEAD) raise WebSocketError(MSG_SOCKET_DEAD) def close(self, code=1000, message=b''): """ Close the websocket and connection, sending the specified code and message. The underlying socket object is _not_ closed, that is the responsibility of the initiator. """ if self.closed: self.current_app.on_close(MSG_ALREADY_CLOSED) try: message = self._encode_bytes(message) self.send_frame(message, opcode=self.OPCODE_CLOSE) except WebSocketError: # Failed to write the closing frame but it's ok because we're # closing the socket anyway. self.logger.debug("Failed to write closing frame -> closing socket") finally: self.logger.debug("Closed WebSocket") self.closed = True self.stream = None self.raw_write = None self.raw_read = None self.environ = None #self.current_app.on_close(MSG_ALREADY_CLOSED) class Stream(object): """ Wraps the handler's socket/rfile attributes and makes it in to a file like object that can be read from/written to by the lower level websocket api. """ __slots__ = ('handler', 'read', 'write') def __init__(self, handler): self.handler = handler self.read = handler.rfile.read self.write = handler.socket.sendall class Header(object): __slots__ = ('fin', 'mask', 'opcode', 'flags', 'length') FIN_MASK = 0x80 OPCODE_MASK = 0x0f MASK_MASK = 0x80 LENGTH_MASK = 0x7f RSV0_MASK = 0x40 RSV1_MASK = 0x20 RSV2_MASK = 0x10 # bitwise mask that will determine the reserved bits for a frame header HEADER_FLAG_MASK = RSV0_MASK | RSV1_MASK | RSV2_MASK def __init__(self, fin=0, opcode=0, flags=0, length=0): self.mask = '' self.fin = fin self.opcode = opcode self.flags = flags self.length = length def mask_payload(self, payload): payload = bytearray(payload) mask = bytearray(self.mask) for i in range_type(self.length): payload[i] ^= mask[i % 4] return payload # it's the same operation unmask_payload = mask_payload def __repr__(self): opcodes = { 0: 'continuation(0)', 1: 'text(1)', 2: 'binary(2)', 8: 'close(8)', 9: 'ping(9)', 10: 'pong(10)' } flags = { 0x40: 'RSV1 MASK', 0x20: 'RSV2 MASK', 0x10: 'RSV3 MASK' } return ("
").format( self.fin, opcodes.get(self.opcode, 'reserved({})'.format(self.opcode)), self.length, flags.get(self.flags, 'reserved({})'.format(self.flags)), self.mask, id(self) ) @classmethod def decode_header(cls, stream): """ Decode a WebSocket header. :param stream: A file like object that can be 'read' from. :returns: A `Header` instance. """ read = stream.read data = read(2) if len(data) != 2: raise WebSocketError("Unexpected EOF while decoding header") first_byte, second_byte = struct.unpack('!BB', data) header = cls( fin=first_byte & cls.FIN_MASK == cls.FIN_MASK, opcode=first_byte & cls.OPCODE_MASK, flags=first_byte & cls.HEADER_FLAG_MASK, length=second_byte & cls.LENGTH_MASK) has_mask = second_byte & cls.MASK_MASK == cls.MASK_MASK if header.opcode > 0x07: if not header.fin: raise ProtocolError( "Received fragmented control frame: {0!r}".format(data)) # Control frames MUST have a payload length of 125 bytes or less if header.length > 125: raise FrameTooLargeException( "Control frame cannot be larger than 125 bytes: " "{0!r}".format(data)) if header.length == 126: # 16 bit length data = read(2) if len(data) != 2: raise WebSocketError('Unexpected EOF while decoding header') header.length = struct.unpack('!H', data)[0] elif header.length == 127: # 64 bit length data = read(8) if len(data) != 8: raise WebSocketError('Unexpected EOF while decoding header') header.length = struct.unpack('!Q', data)[0] if has_mask: mask = read(4) if len(mask) != 4: raise WebSocketError('Unexpected EOF while decoding header') header.mask = mask return header @classmethod def encode_header(cls, fin, opcode, mask, length, flags): """ Encodes a WebSocket header. :param fin: Whether this is the final frame for this opcode. :param opcode: The opcode of the payload, see `OPCODE_*` :param mask: Whether the payload is masked. :param length: The length of the frame. :param flags: The RSV* flags. :return: A bytestring encoded header. """ first_byte = opcode second_byte = 0 extra = b"" result = bytearray() if fin: first_byte |= cls.FIN_MASK if flags & cls.RSV0_MASK: first_byte |= cls.RSV0_MASK if flags & cls.RSV1_MASK: first_byte |= cls.RSV1_MASK if flags & cls.RSV2_MASK: first_byte |= cls.RSV2_MASK # now deal with length complexities if length < 126: second_byte += length elif length <= 0xffff: second_byte += 126 extra = struct.pack('!H', length) elif length <= 0xffffffffffffffff: second_byte += 127 extra = struct.pack('!Q', length) else: raise FrameTooLargeException if mask: second_byte |= cls.MASK_MASK result.append(first_byte) result.append(second_byte) result.extend(extra) if mask: result.extend(mask) return result gevent-websocket-0.10.1/setup.cfg000066400000000000000000000000461351222064600166670ustar00rootroot00000000000000[egg_info] tag_build = tag_date = 0 gevent-websocket-0.10.1/setup.py000066400000000000000000000023431351222064600165620ustar00rootroot00000000000000from setuptools import setup, find_packages version = __import__('geventwebsocket').get_version() setup( name="gevent-websocket", version=version, url="https://www.gitlab.com/noppo/gevent-websocket", author="Jeffrey Gelens", author_email="jeffrey@noppo.pro", description=("Websocket handler for the gevent pywsgi server, a Python " "network library"), long_description=open("README.rst").read(), download_url="https://www.gitlab.com/noppo/gevent-websocket", packages=find_packages(exclude=["examples", "tests"]), license=open('LICENSE').read(), zip_safe=False, install_requires=("gevent"), classifiers=[ "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Topic :: Internet", "Topic :: Software Development :: Libraries :: Python Modules", ] )