diff options
author | Jesusaves <cpntb1@ymail.com> | 2020-12-17 02:50:12 -0300 |
---|---|---|
committer | Jesusaves <cpntb1@ymail.com> | 2020-12-17 02:50:12 -0300 |
commit | e1793335a756f491593a3f36e3d6b6eee2c7a005 (patch) | |
tree | 5a39ccb2a59dc3db6d582a2763ee72649f4b2c8d | |
parent | c707470f1e3b5ddfe82ef09d9b79905d09684ebe (diff) | |
download | client-e1793335a756f491593a3f36e3d6b6eee2c7a005.tar.gz client-e1793335a756f491593a3f36e3d6b6eee2c7a005.tar.bz2 client-e1793335a756f491593a3f36e3d6b6eee2c7a005.tar.xz client-e1793335a756f491593a3f36e3d6b6eee2c7a005.zip |
Replace websocket-client with ws4py
Both are dead but well, Python 2.7 is dead, soooo
47 files changed, 3983 insertions, 4681 deletions
@@ -4,6 +4,7 @@ *.rpyb *.rpyc *.rpymc +*.pyc cache/ saves/ diff --git a/game/01_init.rpy b/game/01_init.rpy index ddc00f4..226c5d1 100644 --- a/game/01_init.rpy +++ b/game/01_init.rpy @@ -16,12 +16,12 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ######################################################################################## -# Definitions +# Definitions (critical appliances, classes, imports, renpy internals, updater) init -3 python: renpy.add_python_directory("python-extra") import requests, zlib, base64, sys, copy, uuid, time, json - import websock as wsock + from ws4py.client.threadedclient import WebSocketClient # set PYTHON_VERSION variable (should be 2713, 3605 could fail) PYTHON_VERSION="%d%d%02d" % (sys.version_info.major, sys.version_info.minor, sys.version_info.micro) diff --git a/game/02_init.rpy b/game/02_init.rpy index fb2749c..147cc91 100644 --- a/game/02_init.rpy +++ b/game/02_init.rpy @@ -16,7 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ######################################################################################## -# Definitions +# Definitions: decode, encode, memory ############################################################################ init -1 python: # Android might have special SSL problems @@ -121,57 +121,6 @@ init -1 python: pass return Player["code"] - def send_packet(packet, args=""): - global tr_load, tr_val, tr_busy - # TODO: if tr_busy already true, wait until it is made false - while tr_busy: - sdelay() - - # Book processing space for ourselves - tr_busy=True - tr_load=False - tr_val=None - - # This is a secret variable which disables threading and queue - # This may cause hangs and other issues. - if persistent.nothreading: - send_packet_now(packet, args) - tr_busy=False - val=tr_val - return val - - # FIXME - timeout=0.0 - renpy.show("spinner", at_list=[truecenter]) - r = send_packet_now(packet, args) - if not r: - # Something went wrong - return ERR_INVALID - - while not tr_load: - sdelay() # FIXME: This can cause errors in mobile? - timeout+=0.02 - - if timeout >= TIMEOUT_INTERVAL: - # FIXME: What if a screen is already being displayed? BUG - try: - renpy.call_screen("msgbox", "Error Code: %d\n\nApplication timeout, click to try again" % (ERR_TIMEOUT)) - timeout=0.0 - except: - if not "ping" in packet.lower(): - stdout("WARNING, ILLEGAL PACKET ON SCREEN TIME: %s" % packet) - pass - - renpy.hide("spinner") - val=tr_val - tr_busy=False - del tr_val - - print "value obtained" - if (val is None): - return ERR_INVALID - return val - def GAME_LOADER(): global allunitsbase, allunits, allquests, allstory, allworld, alltaverns global allnews, tr_load diff --git a/game/03_init.rpy b/game/03_init.rpy index 66b05a3..f082530 100644 --- a/game/03_init.rpy +++ b/game/03_init.rpy @@ -16,7 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ######################################################################################## -# Definitions +# Definitions - Music, Graphics, and JSON data ############################################################################ # This is the JSON data formatter init python: diff --git a/game/04_init.rpy b/game/04_init.rpy index 2becb6f..86a269a 100644 --- a/game/04_init.rpy +++ b/game/04_init.rpy @@ -16,7 +16,7 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ######################################################################################## -# Definitions +# Definitions: WebSocket init python: # FIXME: Drop dead if session_id mismatches (new "token") @@ -52,7 +52,7 @@ init python: tr_load=True tr_val="" tr_busy=False - self.send("PING") + send_packet_now("PING") def onerror(self, err): stdout("ERROR RECEIVED") @@ -60,7 +60,7 @@ init python: stdout("An error happened: %s" % str(err)) # FIXME: If such error happen, the game never dies # This is a huge problem o.o - while True: + try: renpy.call_screen("msgbox", "An unrecoverable error happened.\nPlease close and re-open the app.") # "An unrecoverable error happened.\nPress OK to return to main menu screen.") @@ -70,10 +70,24 @@ init python: # Maybe because it causes an Exception? # I wonder if I can/should run this in a non-daemon thread? # But I sense the problem is not here but a level above + # FIXME: renpy.quit() throws an exception - which in a thread is obviously not gonna work... #renpy.quit(relaunch=True, status=1) + except: sdelay(20.0) return 1 + class GameClient(WebSocketClient): + def opened(self): + onopen(self) + + # FIXME This is also onerror + def closed(self, code, reason=None): + print "Closed down", code, reason + onerror(self, "%s" % str(reason)) + + def received_message(self, m): + onmsg(self, str(m)) + # Be mindful of where/when using this function # Or "onmsg" may accidentally not be cast =/ def send_packet_now(packet, args=""): @@ -83,3 +97,88 @@ init python: ws.send(get_token() + ";" + packet + ";" + args) return + + def wait_packet(): + global tr_load, tr_val, tr_busy + timeout=0.0 + print("Now waiting for packet!") + + while not tr_load: + sdelay() # FIXME: This can cause errors in mobile? + timeout+=0.02 + + if timeout >= TIMEOUT_INTERVAL: + # FIXME: What if a screen is already being displayed? BUG + try: + renpy.call_screen("msgbox", "Error Code: %d\n\nApplication timeout, click to try again" % (ERR_TIMEOUT)) + timeout=0.0 + except: + stdout("ERROR: Timeout and retry failure") + break + + val=tr_val + tr_busy=False + + print("value obtained: %s" % str(val)) + if (val is None): + return ERR_INVALID + return val + + + def schedule_packet(): + global tr_load, tr_val, tr_busy + # TODO: if tr_busy already true, wait until it is made false + while tr_busy: + sdelay() + + # Book processing space for ourselves + tr_busy=True + tr_load=False + tr_val=None + return + + + def send_packet(packet, args=""): + global tr_cmd, tr_busy, tr_load + schedule_packet() + tr_cmd=[packet, args] + tr_val = wait_packet() + return + + # This is not really required, just makes we use two threads + # instead of one? tr_busy is handled by shcedule_packet() which is + # summoned by send_packet() already, so it should not be needed + # I guess it could be handful to auto-restart connection later™? + # sslopt={"cert_reqs": CERT_NONE}) + def supervisor(use_ssl): + global tr_load, tr_val, tr_busy, tr_cmd, ws + stdout(_("Opening new socket...")) + if use_ssl: + ws = GameClient("wss://"+HOST+":61000") + else: + ws = GameClient("ws://"+HOST+":61000") + ws.connect() + renpy.invoke_in_thread(ws.run_forever) # May be problematic + stdout("Connection established!") + + # Begin loop + while True: + try: + if tr_cmd[0] != "": + #schedule_packet() + send_packet_now(tr_cmd[0], tr_cmd[1]) + tr_cmd=["", ""] + # Main thread is the one waiting, not us + time.sleep(0.02) + pass + except NameError: + print("NameError, perhaps the program has terminated?") + break + try: + ws.close() + except: + pass + + # Close the supervisor module + return + diff --git a/game/05_init.rpy b/game/05_init.rpy index 15d24b6..a66dcb8 100644 --- a/game/05_init.rpy +++ b/game/05_init.rpy @@ -16,13 +16,14 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ######################################################################################## -# Definitions +# Definitions - Prestart label prestart: python: tr_busy=True tr_val="" tr_load=False + tr_cmd=["", ""] tr_uptodate=False tr_memcheck=False session_id=uuid.uuid4().hex diff --git a/game/misc.rpy b/game/misc.rpy index 13e6756..8b9fe6f 100644 --- a/game/misc.rpy +++ b/game/misc.rpy @@ -114,6 +114,9 @@ init python: """ + https://github.com/websocket-client/websocket-client/issues/532 + Due to #532 we had to disable SSL... again + https://grandsphere.fandom.com/wiki/Champion_Challenge diff --git a/game/python-extra/enum.py b/game/python-extra/enum.py deleted file mode 100644 index 94020fb..0000000 --- a/game/python-extra/enum.py +++ /dev/null @@ -1,241 +0,0 @@ -# -*- coding: utf-8 -*- - -# enum.py -# Part of ‘enum’, a package providing enumerated types for Python. -# -# Copyright © 2007–2018 Ben Finney <ben+python@benfinney.id.au> -# This is free software: you may copy, modify, and/or distribute this work -# under the terms of the GNU General Public License as published by the -# Free Software Foundation; version 3 of that license or any later version. -# No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details. - -""" Robust enumerated type support in Python. - -This package provides a module for robust enumerations in Python. - -An enumeration object is created with a sequence of string arguments -to the Enum() constructor:: - - >>> from enum import Enum - >>> Colours = Enum('red', 'blue', 'green') - >>> Weekdays = Enum('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun') - -The return value is an immutable sequence object with a value for each -of the string arguments. Each value is also available as an attribute -named from the corresponding string argument:: - - >>> pizza_night = Weekdays[4] - >>> shirt_colour = Colours.green - -The values are constants that can be compared only with values from -the same enumeration; comparison with other values will invoke -Python's fallback comparisons:: - - >>> pizza_night == Weekdays.fri - True - >>> shirt_colour > Colours.red - True - >>> shirt_colour == "green" - False - -Each value from an enumeration exports its sequence index -as an integer, and can be coerced to a simple string matching the -original arguments used to create the enumeration:: - - >>> str(pizza_night) - 'fri' - >>> shirt_colour.index - 2 - -""" - - -__author_name__ = "Ben Finney" -__author_email__ = "ben+python@benfinney.id.au" -__author__ = "%(__author_name__)s <%(__author_email__)s>" % vars() - -_copyright_year_begin = "2007" -__date__ = "2018-08-22" -_copyright_year_latest = __date__.split('-')[0] -_copyright_year_range = _copyright_year_begin -if _copyright_year_latest > _copyright_year_begin: - _copyright_year_range += "–%(_copyright_year_latest)s" % vars() -__copyright__ = ( - "Copyright © %(_copyright_year_range)s" - " %(__author_name__)s") % vars() -__license__ = "GPL-3.0+" - -__url__ = "https://pypi.org/project/enum/" -__version__ = "0.4.7" - - -class EnumException(Exception): - """ Base class for all exceptions in this module. """ - - def __init__(self, *args, **kwargs): - if self.__class__ is EnumException: - class_name = self.__class__.__name__ - raise NotImplementedError( - "%(class_name)s is an abstract base class" % vars()) - super(EnumException, self).__init__(*args, **kwargs) - - -class EnumEmptyError(AssertionError, EnumException): - """ Raised when attempting to create an empty enumeration. """ - - def __str__(self): - return "Enumerations cannot be empty" - - -class EnumBadKeyError(TypeError, EnumException): - """ Raised when creating an Enum with non-string keys. """ - - def __init__(self, key): - self.key = key - - def __str__(self): - return "Enumeration keys must be strings: %(key)r" % vars(self) - - -class EnumImmutableError(TypeError, EnumException): - """ Raised when attempting to modify an Enum. """ - - def __init__(self, *args): - self.args = args - - def __str__(self): - return "Enumeration does not allow modification" - - -def _comparator(func): - """ Decorator for EnumValue rich comparison methods. """ - def comparator_wrapper(self, other): - try: - assert self.enumtype == other.enumtype - result = func(self.index, other.index) - except (AssertionError, AttributeError): - result = NotImplemented - - return result - comparator_wrapper.__name__ = func.__name__ - comparator_wrapper.__doc__ = getattr(float, func.__name__).__doc__ - return comparator_wrapper - -class EnumValue(object): - """ A specific value of an enumerated type. """ - - def __init__(self, enumtype, index, key): - """ Set up a new instance. """ - self._enumtype = enumtype - self._index = index - self._key = key - - @property - def enumtype(self): - return self._enumtype - - @property - def key(self): - return self._key - - def __str__(self): - return str(self.key) - - @property - def index(self): - return self._index - - def __repr__(self): - return "EnumValue(%(_enumtype)r, %(_index)r, %(_key)r)" % vars(self) - - def __hash__(self): - return hash(self._index) - - @_comparator - def __eq__(self, other): - return (self == other) - - @_comparator - def __ne__(self, other): - return (self != other) - - @_comparator - def __lt__(self, other): - return (self < other) - - @_comparator - def __le__(self, other): - return (self <= other) - - @_comparator - def __gt__(self, other): - return (self > other) - - @_comparator - def __ge__(self, other): - return (self >= other) - - -class Enum(object): - """ Enumerated type. """ - - def __init__(self, *keys, **kwargs): - """ Create an enumeration instance. """ - - value_type = kwargs.get('value_type', EnumValue) - - if not keys: - raise EnumEmptyError() - - keys = tuple(keys) - values = [None] * len(keys) - - for i, key in enumerate(keys): - value = value_type(self, i, key) - values[i] = value - try: - super(Enum, self).__setattr__(key, value) - except TypeError: - raise EnumBadKeyError(key) - - self.__dict__['_keys'] = keys - self.__dict__['_values'] = values - - def __setattr__(self, name, value): - raise EnumImmutableError(name) - - def __delattr__(self, name): - raise EnumImmutableError(name) - - def __len__(self): - return len(self._values) - - def __getitem__(self, index): - return self._values[index] - - def __setitem__(self, index, value): - raise EnumImmutableError(index) - - def __delitem__(self, index): - raise EnumImmutableError(index) - - def __iter__(self): - return iter(self._values) - - def __contains__(self, value): - is_member = False - if isinstance(value, basestring): - is_member = (value in self._keys) - else: - is_member = (value in self._values) - return is_member - - -# Local variables: -# mode: python -# time-stamp-format: "%:y-%02m-%02d" -# time-stamp-start: "__date__ = \"" -# time-stamp-end: "\"$" -# time-stamp-line-limit: 200 -# End: -# vim: filetype=python fileencoding=utf-8 : diff --git a/game/python-extra/six.py b/game/python-extra/six.py deleted file mode 100644 index 83f6978..0000000 --- a/game/python-extra/six.py +++ /dev/null @@ -1,982 +0,0 @@ -# Copyright (c) 2010-2020 Benjamin Peterson -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -"""Utilities for writing code that runs on Python 2 and 3""" - -from __future__ import absolute_import - -import functools -import itertools -import operator -import sys -import types - -__author__ = "Benjamin Peterson <benjamin@python.org>" -__version__ = "1.15.0" - - -# Useful for very coarse version differentiation. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -PY34 = sys.version_info[0:2] >= (3, 4) - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) # Invokes __set__. - try: - # This is a bit ugly, but it avoids running this again by - # removing this descriptor. - delattr(obj.__class__, self.name) - except AttributeError: - pass - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - def __getattr__(self, attr): - _module = self._resolve() - value = getattr(_module, attr) - setattr(self, attr, value) - return value - - -class _LazyModule(types.ModuleType): - - def __init__(self, name): - super(_LazyModule, self).__init__(name) - self.__doc__ = self.__class__.__doc__ - - def __dir__(self): - attrs = ["__doc__", "__name__"] - attrs += [attr.name for attr in self._moved_attributes] - return attrs - - # Subclasses should override this - _moved_attributes = [] - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - -class _SixMetaPathImporter(object): - - """ - A meta path importer to import six.moves and its submodules. - - This class implements a PEP302 finder and loader. It should be compatible - with Python 2.5 and all existing versions of Python3 - """ - - def __init__(self, six_module_name): - self.name = six_module_name - self.known_modules = {} - - def _add_module(self, mod, *fullnames): - for fullname in fullnames: - self.known_modules[self.name + "." + fullname] = mod - - def _get_module(self, fullname): - return self.known_modules[self.name + "." + fullname] - - def find_module(self, fullname, path=None): - if fullname in self.known_modules: - return self - return None - - def __get_module(self, fullname): - try: - return self.known_modules[fullname] - except KeyError: - raise ImportError("This loader does not know module " + fullname) - - def load_module(self, fullname): - try: - # in case of a reload - return sys.modules[fullname] - except KeyError: - pass - mod = self.__get_module(fullname) - if isinstance(mod, MovedModule): - mod = mod._resolve() - else: - mod.__loader__ = self - sys.modules[fullname] = mod - return mod - - def is_package(self, fullname): - """ - Return true, if the named module is a package. - - We need this method to get correct spec objects with - Python 3.4 (see PEP451) - """ - return hasattr(self.__get_module(fullname), "__path__") - - def get_code(self, fullname): - """Return None - - Required, if is_package is implemented""" - self.__get_module(fullname) # eventually raises ImportError - return None - get_source = get_code # same as get_code - -_importer = _SixMetaPathImporter(__name__) - - -class _MovedItems(_LazyModule): - - """Lazy loading of moved objects""" - __path__ = [] # mark as package - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("intern", "__builtin__", "sys"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), - MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), - MovedAttribute("getoutput", "commands", "subprocess"), - MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("UserDict", "UserDict", "collections"), - MovedAttribute("UserList", "UserList", "collections"), - MovedAttribute("UserString", "UserString", "collections"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"), - MovedModule("copyreg", "copy_reg"), - MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("_thread", "thread", "_thread"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), - MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), - MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), - MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), -] -# Add windows specific modules. -if sys.platform == "win32": - _moved_attributes += [ - MovedModule("winreg", "_winreg"), - ] - -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) - if isinstance(attr, MovedModule): - _importer._add_module(attr, "moves." + attr.name) -del attr - -_MovedItems._moved_attributes = _moved_attributes - -moves = _MovedItems(__name__ + ".moves") -_importer._add_module(moves, "moves") - - -class Module_six_moves_urllib_parse(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_parse""" - - -_urllib_parse_moved_attributes = [ - MovedAttribute("ParseResult", "urlparse", "urllib.parse"), - MovedAttribute("SplitResult", "urlparse", "urllib.parse"), - MovedAttribute("parse_qs", "urlparse", "urllib.parse"), - MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), - MovedAttribute("urldefrag", "urlparse", "urllib.parse"), - MovedAttribute("urljoin", "urlparse", "urllib.parse"), - MovedAttribute("urlparse", "urlparse", "urllib.parse"), - MovedAttribute("urlsplit", "urlparse", "urllib.parse"), - MovedAttribute("urlunparse", "urlparse", "urllib.parse"), - MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), - MovedAttribute("quote", "urllib", "urllib.parse"), - MovedAttribute("quote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote", "urllib", "urllib.parse"), - MovedAttribute("unquote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), - MovedAttribute("urlencode", "urllib", "urllib.parse"), - MovedAttribute("splitquery", "urllib", "urllib.parse"), - MovedAttribute("splittag", "urllib", "urllib.parse"), - MovedAttribute("splituser", "urllib", "urllib.parse"), - MovedAttribute("splitvalue", "urllib", "urllib.parse"), - MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), - MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), - MovedAttribute("uses_params", "urlparse", "urllib.parse"), - MovedAttribute("uses_query", "urlparse", "urllib.parse"), - MovedAttribute("uses_relative", "urlparse", "urllib.parse"), -] -for attr in _urllib_parse_moved_attributes: - setattr(Module_six_moves_urllib_parse, attr.name, attr) -del attr - -Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes - -_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), - "moves.urllib_parse", "moves.urllib.parse") - - -class Module_six_moves_urllib_error(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_error""" - - -_urllib_error_moved_attributes = [ - MovedAttribute("URLError", "urllib2", "urllib.error"), - MovedAttribute("HTTPError", "urllib2", "urllib.error"), - MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), -] -for attr in _urllib_error_moved_attributes: - setattr(Module_six_moves_urllib_error, attr.name, attr) -del attr - -Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes - -_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), - "moves.urllib_error", "moves.urllib.error") - - -class Module_six_moves_urllib_request(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_request""" - - -_urllib_request_moved_attributes = [ - MovedAttribute("urlopen", "urllib2", "urllib.request"), - MovedAttribute("install_opener", "urllib2", "urllib.request"), - MovedAttribute("build_opener", "urllib2", "urllib.request"), - MovedAttribute("pathname2url", "urllib", "urllib.request"), - MovedAttribute("url2pathname", "urllib", "urllib.request"), - MovedAttribute("getproxies", "urllib", "urllib.request"), - MovedAttribute("Request", "urllib2", "urllib.request"), - MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), - MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), - MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), - MovedAttribute("BaseHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), - MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), - MovedAttribute("FileHandler", "urllib2", "urllib.request"), - MovedAttribute("FTPHandler", "urllib2", "urllib.request"), - MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), - MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), - MovedAttribute("urlretrieve", "urllib", "urllib.request"), - MovedAttribute("urlcleanup", "urllib", "urllib.request"), - MovedAttribute("URLopener", "urllib", "urllib.request"), - MovedAttribute("FancyURLopener", "urllib", "urllib.request"), - MovedAttribute("proxy_bypass", "urllib", "urllib.request"), - MovedAttribute("parse_http_list", "urllib2", "urllib.request"), - MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), -] -for attr in _urllib_request_moved_attributes: - setattr(Module_six_moves_urllib_request, attr.name, attr) -del attr - -Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes - -_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), - "moves.urllib_request", "moves.urllib.request") - - -class Module_six_moves_urllib_response(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_response""" - - -_urllib_response_moved_attributes = [ - MovedAttribute("addbase", "urllib", "urllib.response"), - MovedAttribute("addclosehook", "urllib", "urllib.response"), - MovedAttribute("addinfo", "urllib", "urllib.response"), - MovedAttribute("addinfourl", "urllib", "urllib.response"), -] -for attr in _urllib_response_moved_attributes: - setattr(Module_six_moves_urllib_response, attr.name, attr) -del attr - -Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes - -_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), - "moves.urllib_response", "moves.urllib.response") - - -class Module_six_moves_urllib_robotparser(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_robotparser""" - - -_urllib_robotparser_moved_attributes = [ - MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), -] -for attr in _urllib_robotparser_moved_attributes: - setattr(Module_six_moves_urllib_robotparser, attr.name, attr) -del attr - -Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes - -_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), - "moves.urllib_robotparser", "moves.urllib.robotparser") - - -class Module_six_moves_urllib(types.ModuleType): - - """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" - __path__ = [] # mark as package - parse = _importer._get_module("moves.urllib_parse") - error = _importer._get_module("moves.urllib_error") - request = _importer._get_module("moves.urllib_request") - response = _importer._get_module("moves.urllib_response") - robotparser = _importer._get_module("moves.urllib_robotparser") - - def __dir__(self): - return ['parse', 'error', 'request', 'response', 'robotparser'] - -_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), - "moves.urllib") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - - -if PY3: - def get_unbound_function(unbound): - return unbound - - create_bound_method = types.MethodType - - def create_unbound_method(func, cls): - return func - - Iterator = object -else: - def get_unbound_function(unbound): - return unbound.im_func - - def create_bound_method(func, obj): - return types.MethodType(func, obj, obj.__class__) - - def create_unbound_method(func, cls): - return types.MethodType(func, None, cls) - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -if PY3: - def iterkeys(d, **kw): - return iter(d.keys(**kw)) - - def itervalues(d, **kw): - return iter(d.values(**kw)) - - def iteritems(d, **kw): - return iter(d.items(**kw)) - - def iterlists(d, **kw): - return iter(d.lists(**kw)) - - viewkeys = operator.methodcaller("keys") - - viewvalues = operator.methodcaller("values") - - viewitems = operator.methodcaller("items") -else: - def iterkeys(d, **kw): - return d.iterkeys(**kw) - - def itervalues(d, **kw): - return d.itervalues(**kw) - - def iteritems(d, **kw): - return d.iteritems(**kw) - - def iterlists(d, **kw): - return d.iterlists(**kw) - - viewkeys = operator.methodcaller("viewkeys") - - viewvalues = operator.methodcaller("viewvalues") - - viewitems = operator.methodcaller("viewitems") - -_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") -_add_doc(itervalues, "Return an iterator over the values of a dictionary.") -_add_doc(iteritems, - "Return an iterator over the (key, value) pairs of a dictionary.") -_add_doc(iterlists, - "Return an iterator over the (key, [values]) pairs of a dictionary.") - - -if PY3: - def b(s): - return s.encode("latin-1") - - def u(s): - return s - unichr = chr - import struct - int2byte = struct.Struct(">B").pack - del struct - byte2int = operator.itemgetter(0) - indexbytes = operator.getitem - iterbytes = iter - import io - StringIO = io.StringIO - BytesIO = io.BytesIO - del io - _assertCountEqual = "assertCountEqual" - if sys.version_info[1] <= 1: - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" - _assertNotRegex = "assertNotRegexpMatches" - else: - _assertRaisesRegex = "assertRaisesRegex" - _assertRegex = "assertRegex" - _assertNotRegex = "assertNotRegex" -else: - def b(s): - return s - # Workaround for standalone backslash - - def u(s): - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") - unichr = unichr - int2byte = chr - - def byte2int(bs): - return ord(bs[0]) - - def indexbytes(buf, i): - return ord(buf[i]) - iterbytes = functools.partial(itertools.imap, ord) - import StringIO - StringIO = BytesIO = StringIO.StringIO - _assertCountEqual = "assertItemsEqual" - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" - _assertNotRegex = "assertNotRegexpMatches" -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -def assertCountEqual(self, *args, **kwargs): - return getattr(self, _assertCountEqual)(*args, **kwargs) - - -def assertRaisesRegex(self, *args, **kwargs): - return getattr(self, _assertRaisesRegex)(*args, **kwargs) - - -def assertRegex(self, *args, **kwargs): - return getattr(self, _assertRegex)(*args, **kwargs) - - -def assertNotRegex(self, *args, **kwargs): - return getattr(self, _assertNotRegex)(*args, **kwargs) - - -if PY3: - exec_ = getattr(moves.builtins, "exec") - - def reraise(tp, value, tb=None): - try: - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - finally: - value = None - tb = None - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - exec_("""def reraise(tp, value, tb=None): - try: - raise tp, value, tb - finally: - tb = None -""") - - -if sys.version_info[:2] > (3,): - exec_("""def raise_from(value, from_value): - try: - raise value from from_value - finally: - value = None -""") -else: - def raise_from(value, from_value): - raise value - - -print_ = getattr(moves.builtins, "print", None) -if print_ is None: - def print_(*args, **kwargs): - """The new-style print function for Python 2.4 and 2.5.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - # If the file has an encoding, encode unicode with it. - if (isinstance(fp, file) and - isinstance(data, unicode) and - fp.encoding is not None): - errors = getattr(fp, "errors", None) - if errors is None: - errors = "strict" - data = data.encode(fp.encoding, errors) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) -if sys.version_info[:2] < (3, 3): - _print = print_ - - def print_(*args, **kwargs): - fp = kwargs.get("file", sys.stdout) - flush = kwargs.pop("flush", False) - _print(*args, **kwargs) - if flush and fp is not None: - fp.flush() - -_add_doc(reraise, """Reraise an exception.""") - -if sys.version_info[0:2] < (3, 4): - # This does exactly the same what the :func:`py3:functools.update_wrapper` - # function does on Python versions after 3.2. It sets the ``__wrapped__`` - # attribute on ``wrapper`` object and it doesn't raise an error if any of - # the attributes mentioned in ``assigned`` and ``updated`` are missing on - # ``wrapped`` object. - def _update_wrapper(wrapper, wrapped, - assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - for attr in assigned: - try: - value = getattr(wrapped, attr) - except AttributeError: - continue - else: - setattr(wrapper, attr, value) - for attr in updated: - getattr(wrapper, attr).update(getattr(wrapped, attr, {})) - wrapper.__wrapped__ = wrapped - return wrapper - _update_wrapper.__doc__ = functools.update_wrapper.__doc__ - - def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - return functools.partial(_update_wrapper, wrapped=wrapped, - assigned=assigned, updated=updated) - wraps.__doc__ = functools.wraps.__doc__ - -else: - wraps = functools.wraps - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(type): - - def __new__(cls, name, this_bases, d): - if sys.version_info[:2] >= (3, 7): - # This version introduced PEP 560 that requires a bit - # of extra care (we mimic what is done by __build_class__). - resolved_bases = types.resolve_bases(bases) - if resolved_bases is not bases: - d['__orig_bases__'] = bases - else: - resolved_bases = bases - return meta(name, resolved_bases, d) - - @classmethod - def __prepare__(cls, name, this_bases): - return meta.__prepare__(name, bases) - return type.__new__(metaclass, 'temporary_class', (), {}) - - -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - slots = orig_vars.get('__slots__') - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) - if hasattr(cls, '__qualname__'): - orig_vars['__qualname__'] = cls.__qualname__ - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -def ensure_binary(s, encoding='utf-8', errors='strict'): - """Coerce **s** to six.binary_type. - - For Python 2: - - `unicode` -> encoded to `str` - - `str` -> `str` - - For Python 3: - - `str` -> encoded to `bytes` - - `bytes` -> `bytes` - """ - if isinstance(s, binary_type): - return s - if isinstance(s, text_type): - return s.encode(encoding, errors) - raise TypeError("not expecting type '%s'" % type(s)) - - -def ensure_str(s, encoding='utf-8', errors='strict'): - """Coerce *s* to `str`. - - For Python 2: - - `unicode` -> encoded to `str` - - `str` -> `str` - - For Python 3: - - `str` -> `str` - - `bytes` -> decoded to `str` - """ - # Optimization: Fast return for the common case. - if type(s) is str: - return s - if PY2 and isinstance(s, text_type): - return s.encode(encoding, errors) - elif PY3 and isinstance(s, binary_type): - return s.decode(encoding, errors) - elif not isinstance(s, (text_type, binary_type)): - raise TypeError("not expecting type '%s'" % type(s)) - return s - - -def ensure_text(s, encoding='utf-8', errors='strict'): - """Coerce *s* to six.text_type. - - For Python 2: - - `unicode` -> `unicode` - - `str` -> `unicode` - - For Python 3: - - `str` -> `str` - - `bytes` -> decoded to `str` - """ - if isinstance(s, binary_type): - return s.decode(encoding, errors) - elif isinstance(s, text_type): - return s - else: - raise TypeError("not expecting type '%s'" % type(s)) - - -def python_2_unicode_compatible(klass): - """ - A class decorator that defines __unicode__ and __str__ methods under Python 2. - Under Python 3 it does nothing. - - To support Python 2 and 3 with a single code base, define a __str__ method - returning text and apply this decorator to the class. - """ - if PY2: - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') - return klass - - -# Complete the moves implementation. -# This code is at the end of this module to speed up module loading. -# Turn this module into a package. -__path__ = [] # required for PEP 302 and PEP 451 -__package__ = __name__ # see PEP 366 @ReservedAssignment -if globals().get("__spec__") is not None: - __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable -# Remove other six meta path importers, since they cause problems. This can -# happen if six is removed from sys.modules and then reloaded. (Setuptools does -# this for some reason.) -if sys.meta_path: - for i, importer in enumerate(sys.meta_path): - # Here's some real nastiness: Another "instance" of the six module might - # be floating around. Therefore, we can't use isinstance() to check for - # the six meta path importer, since the other six instance will have - # inserted an importer with different class. - if (type(importer).__name__ == "_SixMetaPathImporter" and - importer.name == __name__): - del sys.meta_path[i] - break - del i, importer -# Finally, add the importer to the meta path import hook. -sys.meta_path.append(_importer) diff --git a/game/python-extra/websock/__init__.py b/game/python-extra/websock/__init__.py deleted file mode 100644 index 605f76c..0000000 --- a/game/python-extra/websock/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" -from ._abnf import * -from ._app import WebSocketApp -from ._core import * -from ._exceptions import * -from ._logging import * -from ._socket import * - -__version__ = "0.57.0" diff --git a/game/python-extra/websock/_abnf.py b/game/python-extra/websock/_abnf.py deleted file mode 100644 index a0000fa..0000000 --- a/game/python-extra/websock/_abnf.py +++ /dev/null @@ -1,447 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" -import array -import os -import struct - -import six - -from ._exceptions import * -from ._utils import validate_utf8 -from threading import Lock - -try: - if six.PY3: - import numpy - else: - numpy = None -except ImportError: - numpy = None - -try: - # If wsaccel is available we use compiled routines to mask data. - if not numpy: - from wsaccel.xormask import XorMaskerSimple - - def _mask(_m, _d): - return XorMaskerSimple(_m).process(_d) -except ImportError: - # wsaccel is not available, we rely on python implementations. - def _mask(_m, _d): - for i in range(len(_d)): - _d[i] ^= _m[i % 4] - - if six.PY3: - return _d.tobytes() - else: - return _d.tostring() - - -__all__ = [ - 'ABNF', 'continuous_frame', 'frame_buffer', - 'STATUS_NORMAL', - 'STATUS_GOING_AWAY', - 'STATUS_PROTOCOL_ERROR', - 'STATUS_UNSUPPORTED_DATA_TYPE', - 'STATUS_STATUS_NOT_AVAILABLE', - 'STATUS_ABNORMAL_CLOSED', - 'STATUS_INVALID_PAYLOAD', - 'STATUS_POLICY_VIOLATION', - 'STATUS_MESSAGE_TOO_BIG', - 'STATUS_INVALID_EXTENSION', - 'STATUS_UNEXPECTED_CONDITION', - 'STATUS_BAD_GATEWAY', - 'STATUS_TLS_HANDSHAKE_ERROR', -] - -# closing frame status codes. -STATUS_NORMAL = 1000 -STATUS_GOING_AWAY = 1001 -STATUS_PROTOCOL_ERROR = 1002 -STATUS_UNSUPPORTED_DATA_TYPE = 1003 -STATUS_STATUS_NOT_AVAILABLE = 1005 -STATUS_ABNORMAL_CLOSED = 1006 -STATUS_INVALID_PAYLOAD = 1007 -STATUS_POLICY_VIOLATION = 1008 -STATUS_MESSAGE_TOO_BIG = 1009 -STATUS_INVALID_EXTENSION = 1010 -STATUS_UNEXPECTED_CONDITION = 1011 -STATUS_BAD_GATEWAY = 1014 -STATUS_TLS_HANDSHAKE_ERROR = 1015 - -VALID_CLOSE_STATUS = ( - STATUS_NORMAL, - STATUS_GOING_AWAY, - STATUS_PROTOCOL_ERROR, - STATUS_UNSUPPORTED_DATA_TYPE, - STATUS_INVALID_PAYLOAD, - STATUS_POLICY_VIOLATION, - STATUS_MESSAGE_TOO_BIG, - STATUS_INVALID_EXTENSION, - STATUS_UNEXPECTED_CONDITION, - STATUS_BAD_GATEWAY, -) - - -class ABNF(object): - """ - ABNF frame class. - see http://tools.ietf.org/html/rfc5234 - and http://tools.ietf.org/html/rfc6455#section-5.2 - """ - - # operation code values. - OPCODE_CONT = 0x0 - OPCODE_TEXT = 0x1 - OPCODE_BINARY = 0x2 - OPCODE_CLOSE = 0x8 - OPCODE_PING = 0x9 - OPCODE_PONG = 0xa - - # available operation code value tuple - OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, - OPCODE_PING, OPCODE_PONG) - - # opcode human readable string - OPCODE_MAP = { - OPCODE_CONT: "cont", - OPCODE_TEXT: "text", - OPCODE_BINARY: "binary", - OPCODE_CLOSE: "close", - OPCODE_PING: "ping", - OPCODE_PONG: "pong" - } - - # data length threshold. - LENGTH_7 = 0x7e - LENGTH_16 = 1 << 16 - LENGTH_63 = 1 << 63 - - def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0, - opcode=OPCODE_TEXT, mask=1, data=""): - """ - Constructor for ABNF. - please check RFC for arguments. - """ - self.fin = fin - self.rsv1 = rsv1 - self.rsv2 = rsv2 - self.rsv3 = rsv3 - self.opcode = opcode - self.mask = mask - if data is None: - data = "" - self.data = data - self.get_mask_key = os.urandom - - def validate(self, skip_utf8_validation=False): - """ - validate the ABNF frame. - skip_utf8_validation: skip utf8 validation. - """ - if self.rsv1 or self.rsv2 or self.rsv3: - raise WebSocketProtocolException("rsv is not implemented, yet") - - if self.opcode not in ABNF.OPCODES: - raise WebSocketProtocolException("Invalid opcode %r", self.opcode) - - if self.opcode == ABNF.OPCODE_PING and not self.fin: - raise WebSocketProtocolException("Invalid ping frame.") - - if self.opcode == ABNF.OPCODE_CLOSE: - l = len(self.data) - if not l: - return - if l == 1 or l >= 126: - raise WebSocketProtocolException("Invalid close frame.") - if l > 2 and not skip_utf8_validation and not validate_utf8(self.data[2:]): - raise WebSocketProtocolException("Invalid close frame.") - - code = 256 * \ - six.byte2int(self.data[0:1]) + six.byte2int(self.data[1:2]) - if not self._is_valid_close_status(code): - raise WebSocketProtocolException("Invalid close opcode.") - - @staticmethod - def _is_valid_close_status(code): - return code in VALID_CLOSE_STATUS or (3000 <= code < 5000) - - def __str__(self): - return "fin=" + str(self.fin) \ - + " opcode=" + str(self.opcode) \ - + " data=" + str(self.data) - - @staticmethod - def create_frame(data, opcode, fin=1): - """ - create frame to send text, binary and other data. - - data: data to send. This is string value(byte array). - if opcode is OPCODE_TEXT and this value is unicode, - data value is converted into unicode string, automatically. - - opcode: operation code. please see OPCODE_XXX. - - fin: fin flag. if set to 0, create continue fragmentation. - """ - if opcode == ABNF.OPCODE_TEXT and isinstance(data, six.text_type): - data = data.encode("utf-8") - # mask must be set if send data from client - return ABNF(fin, 0, 0, 0, opcode, 1, data) - - def format(self): - """ - format this object to string(byte array) to send data to server. - """ - if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]): - raise ValueError("not 0 or 1") - if self.opcode not in ABNF.OPCODES: - raise ValueError("Invalid OPCODE") - length = len(self.data) - if length >= ABNF.LENGTH_63: - raise ValueError("data is too long") - - frame_header = chr(self.fin << 7 - | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 - | self.opcode) - if length < ABNF.LENGTH_7: - frame_header += chr(self.mask << 7 | length) - frame_header = six.b(frame_header) - elif length < ABNF.LENGTH_16: - frame_header += chr(self.mask << 7 | 0x7e) - frame_header = six.b(frame_header) - frame_header += struct.pack("!H", length) - else: - frame_header += chr(self.mask << 7 | 0x7f) - frame_header = six.b(frame_header) - frame_header += struct.pack("!Q", length) - - if not self.mask: - return frame_header + self.data - else: - mask_key = self.get_mask_key(4) - return frame_header + self._get_masked(mask_key) - - def _get_masked(self, mask_key): - s = ABNF.mask(mask_key, self.data) - - if isinstance(mask_key, six.text_type): - mask_key = mask_key.encode('utf-8') - - return mask_key + s - - @staticmethod - def mask(mask_key, data): - """ - mask or unmask data. Just do xor for each byte - - mask_key: 4 byte string(byte). - - data: data to mask/unmask. - """ - if data is None: - data = "" - - if isinstance(mask_key, six.text_type): - mask_key = six.b(mask_key) - - if isinstance(data, six.text_type): - data = six.b(data) - - if numpy: - origlen = len(data) - _mask_key = mask_key[3] << 24 | mask_key[2] << 16 | mask_key[1] << 8 | mask_key[0] - - # We need data to be a multiple of four... - data += bytes(" " * (4 - (len(data) % 4)), "us-ascii") - a = numpy.frombuffer(data, dtype="uint32") - masked = numpy.bitwise_xor(a, [_mask_key]).astype("uint32") - if len(data) > origlen: - return masked.tobytes()[:origlen] - return masked.tobytes() - else: - _m = array.array("B", mask_key) - _d = array.array("B", data) - return _mask(_m, _d) - - -class frame_buffer(object): - _HEADER_MASK_INDEX = 5 - _HEADER_LENGTH_INDEX = 6 - - def __init__(self, recv_fn, skip_utf8_validation): - self.recv = recv_fn - self.skip_utf8_validation = skip_utf8_validation - # Buffers over the packets from the layer beneath until desired amount - # bytes of bytes are received. - self.recv_buffer = [] - self.clear() - self.lock = Lock() - - def clear(self): - self.header = None - self.length = None - self.mask = None - - def has_received_header(self): - return self.header is None - - def recv_header(self): - header = self.recv_strict(2) - b1 = header[0] - - if six.PY2: - b1 = ord(b1) - - fin = b1 >> 7 & 1 - rsv1 = b1 >> 6 & 1 - rsv2 = b1 >> 5 & 1 - rsv3 = b1 >> 4 & 1 - opcode = b1 & 0xf - b2 = header[1] - - if six.PY2: - b2 = ord(b2) - - has_mask = b2 >> 7 & 1 - length_bits = b2 & 0x7f - - self.header = (fin, rsv1, rsv2, rsv3, opcode, has_mask, length_bits) - - def has_mask(self): - if not self.header: - return False - return self.header[frame_buffer._HEADER_MASK_INDEX] - - def has_received_length(self): - return self.length is None - - def recv_length(self): - bits = self.header[frame_buffer._HEADER_LENGTH_INDEX] - length_bits = bits & 0x7f - if length_bits == 0x7e: - v = self.recv_strict(2) - self.length = struct.unpack("!H", v)[0] - elif length_bits == 0x7f: - v = self.recv_strict(8) - self.length = struct.unpack("!Q", v)[0] - else: - self.length = length_bits - - def has_received_mask(self): - return self.mask is None - - def recv_mask(self): - self.mask = self.recv_strict(4) if self.has_mask() else "" - - def recv_frame(self): - - with self.lock: - # Header - if self.has_received_header(): - self.recv_header() - (fin, rsv1, rsv2, rsv3, opcode, has_mask, _) = self.header - - # Frame length - if self.has_received_length(): - self.recv_length() - length = self.length - - # Mask - if self.has_received_mask(): - self.recv_mask() - mask = self.mask - - # Payload - payload = self.recv_strict(length) - if has_mask: - payload = ABNF.mask(mask, payload) - - # Reset for next frame - self.clear() - - frame = ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload) - frame.validate(self.skip_utf8_validation) - - return frame - - def recv_strict(self, bufsize): - shortage = bufsize - sum(len(x) for x in self.recv_buffer) - while shortage > 0: - # Limit buffer size that we pass to socket.recv() to avoid - # fragmenting the heap -- the number of bytes recv() actually - # reads is limited by socket buffer and is relatively small, - # yet passing large numbers repeatedly causes lots of large - # buffers allocated and then shrunk, which results in - # fragmentation. - bytes_ = self.recv(min(16384, shortage)) - self.recv_buffer.append(bytes_) - shortage -= len(bytes_) - - unified = six.b("").join(self.recv_buffer) - - if shortage == 0: - self.recv_buffer = [] - return unified - else: - self.recv_buffer = [unified[bufsize:]] - return unified[:bufsize] - - -class continuous_frame(object): - - def __init__(self, fire_cont_frame, skip_utf8_validation): - self.fire_cont_frame = fire_cont_frame - self.skip_utf8_validation = skip_utf8_validation - self.cont_data = None - self.recving_frames = None - - def validate(self, frame): - if not self.recving_frames and frame.opcode == ABNF.OPCODE_CONT: - raise WebSocketProtocolException("Illegal frame") - if self.recving_frames and \ - frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY): - raise WebSocketProtocolException("Illegal frame") - - def add(self, frame): - if self.cont_data: - self.cont_data[1] += frame.data - else: - if frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY): - self.recving_frames = frame.opcode - self.cont_data = [frame.opcode, frame.data] - - if frame.fin: - self.recving_frames = None - - def is_fire(self, frame): - return frame.fin or self.fire_cont_frame - - def extract(self, frame): - data = self.cont_data - self.cont_data = None - frame.data = data[1] - if not self.fire_cont_frame and data[0] == ABNF.OPCODE_TEXT and not self.skip_utf8_validation and not validate_utf8(frame.data): - raise WebSocketPayloadException( - "cannot decode: " + repr(frame.data)) - - return [data[0], frame] diff --git a/game/python-extra/websock/_app.py b/game/python-extra/websock/_app.py deleted file mode 100644 index e4e9f99..0000000 --- a/game/python-extra/websock/_app.py +++ /dev/null @@ -1,352 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" - -""" -WebSocketApp provides higher level APIs. -""" -import inspect -import select -import sys -import threading -import time -import traceback - -import six - -from ._abnf import ABNF -from ._core import WebSocket, getdefaulttimeout -from ._exceptions import * -from . import _logging - - -__all__ = ["WebSocketApp"] - -class Dispatcher: - def __init__(self, app, ping_timeout): - self.app = app - self.ping_timeout = ping_timeout - - def read(self, sock, read_callback, check_callback): - while self.app.keep_running: - r, w, e = select.select( - (self.app.sock.sock, ), (), (), self.ping_timeout) - if r: - if not read_callback(): - break - check_callback() - -class SSLDispatcher: - def __init__(self, app, ping_timeout): - self.app = app - self.ping_timeout = ping_timeout - - def read(self, sock, read_callback, check_callback): - while self.app.keep_running: - r = self.select() - if r: - if not read_callback(): - break - check_callback() - - def select(self): - sock = self.app.sock.sock - if sock.pending(): - return [sock,] - - r, w, e = select.select((sock, ), (), (), self.ping_timeout) - return r - - -class WebSocketApp(object): - """ - Higher level of APIs are provided. - The interface is like JavaScript WebSocket object. - """ - - def __init__(self, url, header=None, - on_open=None, on_message=None, on_error=None, - on_close=None, on_ping=None, on_pong=None, - on_cont_message=None, - keep_running=True, get_mask_key=None, cookie=None, - subprotocols=None, - on_data=None): - """ - url: websocket url. - header: custom header for websocket handshake. - on_open: callable object which is called at opening websocket. - this function has one argument. The argument is this class object. - on_message: callable object which is called when received data. - on_message has 2 arguments. - The 1st argument is this class object. - The 2nd argument is utf-8 string which we get from the server. - on_error: callable object which is called when we get error. - on_error has 2 arguments. - The 1st argument is this class object. - The 2nd argument is exception object. - on_close: callable object which is called when closed the connection. - this function has one argument. The argument is this class object. - on_cont_message: callback object which is called when receive continued - frame data. - on_cont_message has 3 arguments. - The 1st argument is this class object. - The 2nd argument is utf-8 string which we get from the server. - The 3rd argument is continue flag. if 0, the data continue - to next frame data - on_data: callback object which is called when a message received. - This is called before on_message or on_cont_message, - and then on_message or on_cont_message is called. - on_data has 4 argument. - The 1st argument is this class object. - The 2nd argument is utf-8 string which we get from the server. - The 3rd argument is data type. ABNF.OPCODE_TEXT or ABNF.OPCODE_BINARY will be came. - The 4th argument is continue flag. if 0, the data continue - keep_running: this parameter is obsolete and ignored. - get_mask_key: a callable to produce new mask keys, - see the WebSocket.set_mask_key's docstring for more information - subprotocols: array of available sub protocols. default is None. - """ - self.url = url - self.header = header if header is not None else [] - self.cookie = cookie - - self.on_open = on_open - self.on_message = on_message - self.on_data = on_data - self.on_error = on_error - self.on_close = on_close - self.on_ping = on_ping - self.on_pong = on_pong - self.on_cont_message = on_cont_message - self.keep_running = False - self.get_mask_key = get_mask_key - self.sock = None - self.last_ping_tm = 0 - self.last_pong_tm = 0 - self.subprotocols = subprotocols - - def send(self, data, opcode=ABNF.OPCODE_TEXT): - """ - send message. - data: message to send. If you set opcode to OPCODE_TEXT, - data must be utf-8 string or unicode. - opcode: operation code of data. default is OPCODE_TEXT. - """ - - if not self.sock or self.sock.send(data, opcode) == 0: - raise WebSocketConnectionClosedException( - "Connection is already closed.") - - def close(self, **kwargs): - """ - close websocket connection. - """ - self.keep_running = False - if self.sock: - self.sock.close(**kwargs) - self.sock = None - - def _send_ping(self, interval, event): - while not event.wait(interval): - self.last_ping_tm = time.time() - if self.sock: - try: - self.sock.ping() - except Exception as ex: - _logging.warning("send_ping routine terminated: {}".format(ex)) - break - - def run_forever(self, sockopt=None, sslopt=None, - ping_interval=0, ping_timeout=None, - http_proxy_host=None, http_proxy_port=None, - http_no_proxy=None, http_proxy_auth=None, - skip_utf8_validation=False, - host=None, origin=None, dispatcher=None, - suppress_origin=False, proxy_type=None): - """ - run event loop for WebSocket framework. - This loop is infinite loop and is alive during websocket is available. - sockopt: values for socket.setsockopt. - sockopt must be tuple - and each element is argument of sock.setsockopt. - sslopt: ssl socket optional dict. - ping_interval: automatically send "ping" command - every specified period(second) - if set to 0, not send automatically. - ping_timeout: timeout(second) if the pong message is not received. - http_proxy_host: http proxy host name. - http_proxy_port: http proxy port. If not set, set to 80. - http_no_proxy: host names, which doesn't use proxy. - skip_utf8_validation: skip utf8 validation. - host: update host header. - origin: update origin header. - dispatcher: customize reading data from socket. - suppress_origin: suppress outputting origin header. - - Returns - ------- - False if caught KeyboardInterrupt - True if other exception was raised during a loop - """ - - if ping_timeout is not None and ping_timeout <= 0: - ping_timeout = None - if ping_timeout and ping_interval and ping_interval <= ping_timeout: - raise WebSocketException("Ensure ping_interval > ping_timeout") - if not sockopt: - sockopt = [] - if not sslopt: - sslopt = {} - if self.sock: - raise WebSocketException("socket is already opened") - thread = None - self.keep_running = True - self.last_ping_tm = 0 - self.last_pong_tm = 0 - - def teardown(close_frame=None): - """ - Tears down the connection. - If close_frame is set, we will invoke the on_close handler with the - statusCode and reason from there. - """ - if thread and thread.isAlive(): - event.set() - thread.join() - self.keep_running = False - if self.sock: - self.sock.close() - close_args = self._get_close_args( - close_frame.data if close_frame else None) - self._callback(self.on_close, *close_args) - self.sock = None - - try: - self.sock = WebSocket( - self.get_mask_key, sockopt=sockopt, sslopt=sslopt, - fire_cont_frame=self.on_cont_message is not None, - skip_utf8_validation=skip_utf8_validation, - enable_multithread=True if ping_interval else False) - self.sock.settimeout(getdefaulttimeout()) - self.sock.connect( - self.url, header=self.header, cookie=self.cookie, - http_proxy_host=http_proxy_host, - http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy, - http_proxy_auth=http_proxy_auth, subprotocols=self.subprotocols, - host=host, origin=origin, suppress_origin=suppress_origin, - proxy_type=proxy_type) - if not dispatcher: - dispatcher = self.create_dispatcher(ping_timeout) - - self._callback(self.on_open) - - if ping_interval: - event = threading.Event() - thread = threading.Thread( - target=self._send_ping, args=(ping_interval, event)) - thread.setDaemon(True) - thread.start() - - def read(): - if not self.keep_running: - return teardown() - - op_code, frame = self.sock.recv_data_frame(True) - if op_code == ABNF.OPCODE_CLOSE: - return teardown(frame) - elif op_code == ABNF.OPCODE_PING: - self._callback(self.on_ping, frame.data) - elif op_code == ABNF.OPCODE_PONG: - self.last_pong_tm = time.time() - self._callback(self.on_pong, frame.data) - elif op_code == ABNF.OPCODE_CONT and self.on_cont_message: - self._callback(self.on_data, frame.data, - frame.opcode, frame.fin) - self._callback(self.on_cont_message, - frame.data, frame.fin) - else: - data = frame.data - if six.PY3 and op_code == ABNF.OPCODE_TEXT: - data = data.decode("utf-8") - self._callback(self.on_data, data, frame.opcode, True) - self._callback(self.on_message, data) - - return True - - def check(): - if (ping_timeout): - has_timeout_expired = time.time() - self.last_ping_tm > ping_timeout - has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0 - has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > ping_timeout - - if (self.last_ping_tm - and has_timeout_expired - and (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)): - raise WebSocketTimeoutException("ping/pong timed out") - return True - - dispatcher.read(self.sock.sock, read, check) - except (Exception, KeyboardInterrupt, SystemExit) as e: - self._callback(self.on_error, e) - if isinstance(e, SystemExit): - # propagate SystemExit further - raise - teardown() - return not isinstance(e, KeyboardInterrupt) - - def create_dispatcher(self, ping_timeout): - timeout = ping_timeout or 10 - if self.sock.is_ssl(): - return SSLDispatcher(self, timeout) - - return Dispatcher(self, timeout) - - def _get_close_args(self, data): - """ this functions extracts the code, reason from the close body - if they exists, and if the self.on_close except three arguments """ - # if the on_close callback is "old", just return empty list - if sys.version_info < (3, 0): - if not self.on_close or len(inspect.getargspec(self.on_close).args) != 3: - return [] - else: - if not self.on_close or len(inspect.getfullargspec(self.on_close).args) != 3: - return [] - - if data and len(data) >= 2: - code = 256 * six.byte2int(data[0:1]) + six.byte2int(data[1:2]) - reason = data[2:].decode('utf-8') - return [code, reason] - - return [None, None] - - def _callback(self, callback, *args): - if callback: - try: - if inspect.ismethod(callback): - callback(*args) - else: - callback(self, *args) - - except Exception as e: - _logging.error("error from callback {}: {}".format(callback, e)) - if _logging.isEnabledForDebug(): - _, _, tb = sys.exc_info() - traceback.print_tb(tb) diff --git a/game/python-extra/websock/_cookiejar.py b/game/python-extra/websock/_cookiejar.py deleted file mode 100644 index 3efeb0f..0000000 --- a/game/python-extra/websock/_cookiejar.py +++ /dev/null @@ -1,52 +0,0 @@ -try: - import Cookie -except: - import http.cookies as Cookie - - -class SimpleCookieJar(object): - def __init__(self): - self.jar = dict() - - def add(self, set_cookie): - if set_cookie: - try: - simpleCookie = Cookie.SimpleCookie(set_cookie) - except: - simpleCookie = Cookie.SimpleCookie(set_cookie.encode('ascii', 'ignore')) - - for k, v in simpleCookie.items(): - domain = v.get("domain") - if domain: - if not domain.startswith("."): - domain = "." + domain - cookie = self.jar.get(domain) if self.jar.get(domain) else Cookie.SimpleCookie() - cookie.update(simpleCookie) - self.jar[domain.lower()] = cookie - - def set(self, set_cookie): - if set_cookie: - try: - simpleCookie = Cookie.SimpleCookie(set_cookie) - except: - simpleCookie = Cookie.SimpleCookie(set_cookie.encode('ascii', 'ignore')) - - for k, v in simpleCookie.items(): - domain = v.get("domain") - if domain: - if not domain.startswith("."): - domain = "." + domain - self.jar[domain.lower()] = simpleCookie - - def get(self, host): - if not host: - return "" - - cookies = [] - for domain, simpleCookie in self.jar.items(): - host = host.lower() - if host.endswith(domain) or host == domain[1:]: - cookies.append(self.jar.get(domain)) - - return "; ".join(filter(None, ["%s=%s" % (k, v.value) for cookie in filter(None, sorted(cookies)) for k, v in - sorted(cookie.items())])) diff --git a/game/python-extra/websock/_core.py b/game/python-extra/websock/_core.py deleted file mode 100644 index 6442188..0000000 --- a/game/python-extra/websock/_core.py +++ /dev/null @@ -1,522 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" -from __future__ import print_function - -import socket -import struct -import threading -import time - -import six - -# websocket modules -from ._abnf import * -from ._exceptions import * -from ._handshake import * -from ._http import * -from ._logging import * -from ._socket import * -from ._ssl_compat import * -from ._utils import * - -__all__ = ['WebSocket', 'create_connection'] - -""" -websocket python client. -========================= - -This version support only hybi-13. -Please see http://tools.ietf.org/html/rfc6455 for protocol. -""" - - -class WebSocket(object): - """ - Low level WebSocket interface. - This class is based on - The WebSocket protocol draft-hixie-thewebsocketprotocol-76 - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 - - We can connect to the websocket server and send/receive data. - The following example is an echo client. - - >>> import websocket - >>> ws = websocket.WebSocket() - >>> ws.connect("ws://echo.websocket.org") - >>> ws.send("Hello, Server") - >>> ws.recv() - 'Hello, Server' - >>> ws.close() - - get_mask_key: a callable to produce new mask keys, see the set_mask_key - function's docstring for more details - sockopt: values for socket.setsockopt. - sockopt must be tuple and each element is argument of sock.setsockopt. - sslopt: dict object for ssl socket option. - fire_cont_frame: fire recv event for each cont frame. default is False - enable_multithread: if set to True, lock send method. - skip_utf8_validation: skip utf8 validation. - """ - - def __init__(self, get_mask_key=None, sockopt=None, sslopt=None, - fire_cont_frame=False, enable_multithread=False, - skip_utf8_validation=False, **_): - """ - Initialize WebSocket object. - """ - self.sock_opt = sock_opt(sockopt, sslopt) - self.handshake_response = None - self.sock = None - - self.connected = False - self.get_mask_key = get_mask_key - # These buffer over the build-up of a single frame. - self.frame_buffer = frame_buffer(self._recv, skip_utf8_validation) - self.cont_frame = continuous_frame( - fire_cont_frame, skip_utf8_validation) - - if enable_multithread: - self.lock = threading.Lock() - self.readlock = threading.Lock() - else: - self.lock = NoLock() - self.readlock = NoLock() - - def __iter__(self): - """ - Allow iteration over websocket, implying sequential `recv` executions. - """ - while True: - yield self.recv() - - def __next__(self): - return self.recv() - - def next(self): - return self.__next__() - - def fileno(self): - return self.sock.fileno() - - def set_mask_key(self, func): - """ - set function to create musk key. You can customize mask key generator. - Mainly, this is for testing purpose. - - func: callable object. the func takes 1 argument as integer. - The argument means length of mask key. - This func must return string(byte array), - which length is argument specified. - """ - self.get_mask_key = func - - def gettimeout(self): - """ - Get the websocket timeout(second). - """ - return self.sock_opt.timeout - - def settimeout(self, timeout): - """ - Set the timeout to the websocket. - - timeout: timeout time(second). - """ - self.sock_opt.timeout = timeout - if self.sock: - self.sock.settimeout(timeout) - - timeout = property(gettimeout, settimeout) - - def getsubprotocol(self): - """ - get subprotocol - """ - if self.handshake_response: - return self.handshake_response.subprotocol - else: - return None - - subprotocol = property(getsubprotocol) - - def getstatus(self): - """ - get handshake status - """ - if self.handshake_response: - return self.handshake_response.status - else: - return None - - status = property(getstatus) - - def getheaders(self): - """ - get handshake response header - """ - if self.handshake_response: - return self.handshake_response.headers - else: - return None - - def is_ssl(self): - ########################## - # THIS HAS BEEN MODIFIED # - ########################## - try: - return isinstance(self.sock, ssl.SSLSocket) - except: - return False - - headers = property(getheaders) - - def connect(self, url, **options): - """ - Connect to url. url is websocket url scheme. - ie. ws://host:port/resource - You can customize using 'options'. - If you set "header" list object, you can set your own custom header. - - >>> ws = WebSocket() - >>> ws.connect("ws://echo.websocket.org/", - ... header=["User-Agent: MyProgram", - ... "x-custom: header"]) - - timeout: socket timeout time. This value is integer. - if you set None for this value, - it means "use default_timeout value" - - options: "header" -> custom http header list or dict. - "cookie" -> cookie value. - "origin" -> custom origin url. - "suppress_origin" -> suppress outputting origin header. - "host" -> custom host header string. - "http_proxy_host" - http proxy host name. - "http_proxy_port" - http proxy port. If not set, set to 80. - "http_no_proxy" - host names, which doesn't use proxy. - "http_proxy_auth" - http proxy auth information. - tuple of username and password. - default is None - "redirect_limit" -> number of redirects to follow. - "subprotocols" - array of available sub protocols. - default is None. - "socket" - pre-initialized stream socket. - - """ - # FIXME: "subprotocols" are getting lost, not passed down - # FIXME: "header", "cookie", "origin" and "host" too - self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout) - self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), - options.pop('socket', None)) - - try: - self.handshake_response = handshake(self.sock, *addrs, **options) - for attempt in range(options.pop('redirect_limit', 3)): - if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES: - url = self.handshake_response.headers['location'] - self.sock.close() - self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), - options.pop('socket', None)) - self.handshake_response = handshake(self.sock, *addrs, **options) - self.connected = True - except: - if self.sock: - self.sock.close() - self.sock = None - raise - - def send(self, payload, opcode=ABNF.OPCODE_TEXT): - """ - Send the data as string. - - payload: Payload must be utf-8 string or unicode, - if the opcode is OPCODE_TEXT. - Otherwise, it must be string(byte array) - - opcode: operation code to send. Please see OPCODE_XXX. - """ - - frame = ABNF.create_frame(payload, opcode) - return self.send_frame(frame) - - def send_frame(self, frame): - """ - Send the data frame. - - frame: frame data created by ABNF.create_frame - - >>> ws = create_connection("ws://echo.websocket.org/") - >>> frame = ABNF.create_frame("Hello", ABNF.OPCODE_TEXT) - >>> ws.send_frame(frame) - >>> cont_frame = ABNF.create_frame("My name is ", ABNF.OPCODE_CONT, 0) - >>> ws.send_frame(frame) - >>> cont_frame = ABNF.create_frame("Foo Bar", ABNF.OPCODE_CONT, 1) - >>> ws.send_frame(frame) - - """ - if self.get_mask_key: - frame.get_mask_key = self.get_mask_key - data = frame.format() - length = len(data) - if (isEnabledForTrace()): - trace("send: " + repr(data)) - - with self.lock: - while data: - l = self._send(data) - data = data[l:] - - return length - - def send_binary(self, payload): - return self.send(payload, ABNF.OPCODE_BINARY) - - def ping(self, payload=""): - """ - send ping data. - - payload: data payload to send server. - """ - if isinstance(payload, six.text_type): - payload = payload.encode("utf-8") - self.send(payload, ABNF.OPCODE_PING) - - def pong(self, payload): - """ - send pong data. - - payload: data payload to send server. - """ - if isinstance(payload, six.text_type): - payload = payload.encode("utf-8") - self.send(payload, ABNF.OPCODE_PONG) - - def recv(self): - """ - Receive string data(byte array) from the server. - - return value: string(byte array) value. - """ - with self.readlock: - opcode, data = self.recv_data() - if six.PY3 and opcode == ABNF.OPCODE_TEXT: - return data.decode("utf-8") - elif opcode == ABNF.OPCODE_TEXT or opcode == ABNF.OPCODE_BINARY: - return data - else: - return '' - - def recv_data(self, control_frame=False): - """ - Receive data with operation code. - - control_frame: a boolean flag indicating whether to return control frame - data, defaults to False - - return value: tuple of operation code and string(byte array) value. - """ - opcode, frame = self.recv_data_frame(control_frame) - return opcode, frame.data - - def recv_data_frame(self, control_frame=False): - """ - Receive data with operation code. - - control_frame: a boolean flag indicating whether to return control frame - data, defaults to False - - return value: tuple of operation code and string(byte array) value. - """ - while True: - frame = self.recv_frame() - if not frame: - # handle error: - # 'NoneType' object has no attribute 'opcode' - raise WebSocketProtocolException( - "Not a valid frame %s" % frame) - elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT): - self.cont_frame.validate(frame) - self.cont_frame.add(frame) - - if self.cont_frame.is_fire(frame): - return self.cont_frame.extract(frame) - - elif frame.opcode == ABNF.OPCODE_CLOSE: - self.send_close() - return frame.opcode, frame - elif frame.opcode == ABNF.OPCODE_PING: - if len(frame.data) < 126: - self.pong(frame.data) - else: - raise WebSocketProtocolException( - "Ping message is too long") - if control_frame: - return frame.opcode, frame - elif frame.opcode == ABNF.OPCODE_PONG: - if control_frame: - return frame.opcode, frame - - def recv_frame(self): - """ - receive data as frame from server. - - return value: ABNF frame object. - """ - return self.frame_buffer.recv_frame() - - def send_close(self, status=STATUS_NORMAL, reason=six.b("")): - """ - send close data to the server. - - status: status code to send. see STATUS_XXX. - - reason: the reason to close. This must be string or bytes. - """ - if status < 0 or status >= ABNF.LENGTH_16: - raise ValueError("code is invalid range") - self.connected = False - self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) - - def close(self, status=STATUS_NORMAL, reason=six.b(""), timeout=3): - """ - Close Websocket object - - status: status code to send. see STATUS_XXX. - - reason: the reason to close. This must be string. - - timeout: timeout until receive a close frame. - If None, it will wait forever until receive a close frame. - """ - if self.connected: - if status < 0 or status >= ABNF.LENGTH_16: - raise ValueError("code is invalid range") - - try: - self.connected = False - self.send(struct.pack('!H', status) + - reason, ABNF.OPCODE_CLOSE) - sock_timeout = self.sock.gettimeout() - self.sock.settimeout(timeout) - start_time = time.time() - while timeout is None or time.time() - start_time < timeout: - try: - frame = self.recv_frame() - if frame.opcode != ABNF.OPCODE_CLOSE: - continue - if isEnabledForError(): - recv_status = struct.unpack("!H", frame.data[0:2])[0] - if recv_status != STATUS_NORMAL: - error("close status: " + repr(recv_status)) - break - except: - break - self.sock.settimeout(sock_timeout) - self.sock.shutdown(socket.SHUT_RDWR) - except: - pass - - self.shutdown() - - def abort(self): - """ - Low-level asynchronous abort, wakes up other threads that are waiting in recv_* - """ - if self.connected: - self.sock.shutdown(socket.SHUT_RDWR) - - def shutdown(self): - """close socket, immediately.""" - if self.sock: - self.sock.close() - self.sock = None - self.connected = False - - def _send(self, data): - return send(self.sock, data) - - def _recv(self, bufsize): - try: - return recv(self.sock, bufsize) - except WebSocketConnectionClosedException: - if self.sock: - self.sock.close() - self.sock = None - self.connected = False - raise - - -def create_connection(url, timeout=None, class_=WebSocket, **options): - """ - connect to url and return websocket object. - - Connect to url and return the WebSocket object. - Passing optional timeout parameter will set the timeout on the socket. - If no timeout is supplied, - the global default timeout setting returned by getdefauttimeout() is used. - You can customize using 'options'. - If you set "header" list object, you can set your own custom header. - - >>> conn = create_connection("ws://echo.websocket.org/", - ... header=["User-Agent: MyProgram", - ... "x-custom: header"]) - - - timeout: socket timeout time. This value is integer. - if you set None for this value, - it means "use default_timeout value" - - class_: class to instantiate when creating the connection. It has to implement - settimeout and connect. It's __init__ should be compatible with - WebSocket.__init__, i.e. accept all of it's kwargs. - options: "header" -> custom http header list or dict. - "cookie" -> cookie value. - "origin" -> custom origin url. - "suppress_origin" -> suppress outputting origin header. - "host" -> custom host header string. - "http_proxy_host" - http proxy host name. - "http_proxy_port" - http proxy port. If not set, set to 80. - "http_no_proxy" - host names, which doesn't use proxy. - "http_proxy_auth" - http proxy auth information. - tuple of username and password. - default is None - "enable_multithread" -> enable lock for multithread. - "redirect_limit" -> number of redirects to follow. - "sockopt" -> socket options - "sslopt" -> ssl option - "subprotocols" - array of available sub protocols. - default is None. - "skip_utf8_validation" - skip utf8 validation. - "socket" - pre-initialized stream socket. - """ - sockopt = options.pop("sockopt", []) - sslopt = options.pop("sslopt", {}) - fire_cont_frame = options.pop("fire_cont_frame", False) - enable_multithread = options.pop("enable_multithread", False) - skip_utf8_validation = options.pop("skip_utf8_validation", False) - websock = class_(sockopt=sockopt, sslopt=sslopt, - fire_cont_frame=fire_cont_frame, - enable_multithread=enable_multithread, - skip_utf8_validation=skip_utf8_validation, **options) - websock.settimeout(timeout if timeout is not None else getdefaulttimeout()) - websock.connect(url, **options) - return websock diff --git a/game/python-extra/websock/_exceptions.py b/game/python-extra/websock/_exceptions.py deleted file mode 100644 index 2070790..0000000 --- a/game/python-extra/websock/_exceptions.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" - - -""" -define websocket exceptions -""" - - -class WebSocketException(Exception): - """ - websocket exception class. - """ - pass - - -class WebSocketProtocolException(WebSocketException): - """ - If the websocket protocol is invalid, this exception will be raised. - """ - pass - - -class WebSocketPayloadException(WebSocketException): - """ - If the websocket payload is invalid, this exception will be raised. - """ - pass - - -class WebSocketConnectionClosedException(WebSocketException): - """ - If remote host closed the connection or some network error happened, - this exception will be raised. - """ - pass - - -class WebSocketTimeoutException(WebSocketException): - """ - WebSocketTimeoutException will be raised at socket timeout during read/write data. - """ - pass - - -class WebSocketProxyException(WebSocketException): - """ - WebSocketProxyException will be raised when proxy error occurred. - """ - pass - - -class WebSocketBadStatusException(WebSocketException): - """ - WebSocketBadStatusException will be raised when we get bad handshake status code. - """ - - def __init__(self, message, status_code, status_message=None, resp_headers=None): - msg = message % (status_code, status_message) - super(WebSocketBadStatusException, self).__init__(msg) - self.status_code = status_code - self.resp_headers = resp_headers - - -class WebSocketAddressException(WebSocketException): - """ - If the websocket address info cannot be found, this exception will be raised. - """ - pass diff --git a/game/python-extra/websock/_handshake.py b/game/python-extra/websock/_handshake.py deleted file mode 100644 index 7476a07..0000000 --- a/game/python-extra/websock/_handshake.py +++ /dev/null @@ -1,211 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" -import hashlib -import hmac -import os - -import six - -from ._cookiejar import SimpleCookieJar -from ._exceptions import * -from ._http import * -from ._logging import * -from ._socket import * - -if hasattr(six, 'PY3') and six.PY3: - from base64 import encodebytes as base64encode -else: - from base64 import encodestring as base64encode - -if hasattr(six, 'PY3') and six.PY3: - if hasattr(six, 'PY34') and six.PY34: - from http import client as HTTPStatus - else: - from http import HTTPStatus -else: - import httplib as HTTPStatus - -__all__ = ["handshake_response", "handshake", "SUPPORTED_REDIRECT_STATUSES"] - -if hasattr(hmac, "compare_digest"): - compare_digest = hmac.compare_digest -else: - def compare_digest(s1, s2): - return s1 == s2 - -# websocket supported version. -VERSION = 13 - -SUPPORTED_REDIRECT_STATUSES = (HTTPStatus.MOVED_PERMANENTLY, HTTPStatus.FOUND, HTTPStatus.SEE_OTHER,) -SUCCESS_STATUSES = SUPPORTED_REDIRECT_STATUSES + (HTTPStatus.SWITCHING_PROTOCOLS,) - -CookieJar = SimpleCookieJar() - - -class handshake_response(object): - - def __init__(self, status, headers, subprotocol): - self.status = status - self.headers = headers - self.subprotocol = subprotocol - CookieJar.add(headers.get("set-cookie")) - - -def handshake(sock, hostname, port, resource, **options): - headers, key = _get_handshake_headers(resource, hostname, port, options) - - header_str = "\r\n".join(headers) - send(sock, header_str) - dump("request header", header_str) - - status, resp = _get_resp_headers(sock) - if status in SUPPORTED_REDIRECT_STATUSES: - return handshake_response(status, resp, None) - success, subproto = _validate(resp, key, options.get("subprotocols")) - if not success: - raise WebSocketException("Invalid WebSocket Header") - - return handshake_response(status, resp, subproto) - - -def _pack_hostname(hostname): - # IPv6 address - if ':' in hostname: - return '[' + hostname + ']' - - return hostname - -def _get_handshake_headers(resource, host, port, options): - headers = [ - "GET %s HTTP/1.1" % resource, - "Upgrade: websocket" - ] - if port == 80 or port == 443: - hostport = _pack_hostname(host) - else: - hostport = "%s:%d" % (_pack_hostname(host), port) - if "host" in options and options["host"] is not None: - headers.append("Host: %s" % options["host"]) - else: - headers.append("Host: %s" % hostport) - - if "suppress_origin" not in options or not options["suppress_origin"]: - if "origin" in options and options["origin"] is not None: - headers.append("Origin: %s" % options["origin"]) - else: - headers.append("Origin: http://%s" % hostport) - - key = _create_sec_websocket_key() - - # Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified - if not 'header' in options or 'Sec-WebSocket-Key' not in options['header']: - key = _create_sec_websocket_key() - headers.append("Sec-WebSocket-Key: %s" % key) - else: - key = options['header']['Sec-WebSocket-Key'] - - if not 'header' in options or 'Sec-WebSocket-Version' not in options['header']: - headers.append("Sec-WebSocket-Version: %s" % VERSION) - - if not 'connection' in options or options['connection'] is None: - headers.append('Connection: upgrade') - else: - headers.append(options['connection']) - - subprotocols = options.get("subprotocols") - if subprotocols: - headers.append("Sec-WebSocket-Protocol: %s" % ",".join(subprotocols)) - - if "header" in options: - header = options["header"] - if isinstance(header, dict): - header = [ - ": ".join([k, v]) - for k, v in header.items() - if v is not None - ] - headers.extend(header) - - server_cookie = CookieJar.get(host) - client_cookie = options.get("cookie", None) - - cookie = "; ".join(filter(None, [server_cookie, client_cookie])) - - if cookie: - headers.append("Cookie: %s" % cookie) - - headers.append("") - headers.append("") - - return headers, key - - -def _get_resp_headers(sock, success_statuses=SUCCESS_STATUSES): - status, resp_headers, status_message = read_headers(sock) - if status not in success_statuses: - raise WebSocketBadStatusException("Handshake status %d %s", status, status_message, resp_headers) - return status, resp_headers - - -_HEADERS_TO_CHECK = { - "upgrade": "websocket", - "connection": "upgrade", -} - - -def _validate(headers, key, subprotocols): - subproto = None - for k, v in _HEADERS_TO_CHECK.items(): - r = headers.get(k, None) - if not r: - return False, None - r = r.lower() - if v != r: - return False, None - - if subprotocols: - subproto = headers.get("sec-websocket-protocol", None).lower() - if not subproto or subproto not in [s.lower() for s in subprotocols]: - error("Invalid subprotocol: " + str(subprotocols)) - return False, None - - result = headers.get("sec-websocket-accept", None) - if not result: - return False, None - result = result.lower() - - if isinstance(result, six.text_type): - result = result.encode('utf-8') - - value = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode('utf-8') - hashed = base64encode(hashlib.sha1(value).digest()).strip().lower() - success = compare_digest(hashed, result) - - if success: - return True, subproto - else: - return False, None - - -def _create_sec_websocket_key(): - randomness = os.urandom(16) - return base64encode(randomness).decode('utf-8').strip() diff --git a/game/python-extra/websock/_http.py b/game/python-extra/websock/_http.py deleted file mode 100644 index a8777de..0000000 --- a/game/python-extra/websock/_http.py +++ /dev/null @@ -1,330 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" -import errno -import os -import socket -import sys - -import six - -from ._exceptions import * -from ._logging import * -from ._socket import* -from ._ssl_compat import * -from ._url import * - -if six.PY3: - from base64 import encodebytes as base64encode -else: - from base64 import encodestring as base64encode - -__all__ = ["proxy_info", "connect", "read_headers"] - -try: - import socks - ProxyConnectionError = socks.ProxyConnectionError - HAS_PYSOCKS = True -except: - class ProxyConnectionError(BaseException): - pass - HAS_PYSOCKS = False - -class proxy_info(object): - - def __init__(self, **options): - self.type = options.get("proxy_type") or "http" - if not(self.type in ['http', 'socks4', 'socks5', 'socks5h']): - raise ValueError("proxy_type must be 'http', 'socks4', 'socks5' or 'socks5h'") - self.host = options.get("http_proxy_host", None) - if self.host: - self.port = options.get("http_proxy_port", 0) - self.auth = options.get("http_proxy_auth", None) - self.no_proxy = options.get("http_no_proxy", None) - else: - self.port = 0 - self.auth = None - self.no_proxy = None - - -def _open_proxied_socket(url, options, proxy): - hostname, port, resource, is_secure = parse_url(url) - - if not HAS_PYSOCKS: - raise WebSocketException("PySocks module not found.") - - ptype = socks.SOCKS5 - rdns = False - if proxy.type == "socks4": - ptype = socks.SOCKS4 - if proxy.type == "http": - ptype = socks.HTTP - if proxy.type[-1] == "h": - rdns = True - - sock = socks.create_connection( - (hostname, port), - proxy_type = ptype, - proxy_addr = proxy.host, - proxy_port = proxy.port, - proxy_rdns = rdns, - proxy_username = proxy.auth[0] if proxy.auth else None, - proxy_password = proxy.auth[1] if proxy.auth else None, - timeout = options.timeout, - socket_options = DEFAULT_SOCKET_OPTION + options.sockopt - ) - - if is_secure: - if HAVE_SSL: - sock = _ssl_socket(sock, options.sslopt, hostname) - else: - raise WebSocketException("SSL not available.") - - return sock, (hostname, port, resource) - - -def connect(url, options, proxy, socket): - if proxy.host and not socket and not (proxy.type == 'http'): - return _open_proxied_socket(url, options, proxy) - - hostname, port, resource, is_secure = parse_url(url) - - if socket: - return socket, (hostname, port, resource) - - addrinfo_list, need_tunnel, auth = _get_addrinfo_list( - hostname, port, is_secure, proxy) - if not addrinfo_list: - raise WebSocketException( - "Host not found.: " + hostname + ":" + str(port)) - - sock = None - try: - sock = _open_socket(addrinfo_list, options.sockopt, options.timeout) - if need_tunnel: - sock = _tunnel(sock, hostname, port, auth) - - if is_secure: - if HAVE_SSL: - sock = _ssl_socket(sock, options.sslopt, hostname) - else: - raise WebSocketException("SSL not available.") - - return sock, (hostname, port, resource) - except: - if sock: - sock.close() - raise - - -def _get_addrinfo_list(hostname, port, is_secure, proxy): - phost, pport, pauth = get_proxy_info( - hostname, is_secure, proxy.host, proxy.port, proxy.auth, proxy.no_proxy) - try: - # when running on windows 10, getaddrinfo without socktype returns a socktype 0. - # This generates an error exception: `_on_error: exception Socket type must be stream or datagram, not 0` - # or `OSError: [Errno 22] Invalid argument` when creating socket. Force the socket type to SOCK_STREAM. - if not phost: - addrinfo_list = socket.getaddrinfo( - hostname, port, 0, socket.SOCK_STREAM, socket.SOL_TCP) - return addrinfo_list, False, None - else: - pport = pport and pport or 80 - # when running on windows 10, the getaddrinfo used above - # returns a socktype 0. This generates an error exception: - # _on_error: exception Socket type must be stream or datagram, not 0 - # Force the socket type to SOCK_STREAM - addrinfo_list = socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP) - return addrinfo_list, True, pauth - except socket.gaierror as e: - raise WebSocketAddressException(e) - - -def _open_socket(addrinfo_list, sockopt, timeout): - err = None - for addrinfo in addrinfo_list: - family, socktype, proto = addrinfo[:3] - sock = socket.socket(family, socktype, proto) - sock.settimeout(timeout) - for opts in DEFAULT_SOCKET_OPTION: - sock.setsockopt(*opts) - for opts in sockopt: - sock.setsockopt(*opts) - - address = addrinfo[4] - err = None - while not err: - try: - sock.connect(address) - except ProxyConnectionError as error: - err = WebSocketProxyException(str(error)) - err.remote_ip = str(address[0]) - continue - except socket.error as error: - error.remote_ip = str(address[0]) - try: - eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED) - except: - eConnRefused = (errno.ECONNREFUSED, ) - if error.errno == errno.EINTR: - continue - elif error.errno in eConnRefused: - err = error - continue - else: - raise error - else: - break - else: - continue - break - else: - if err: - raise err - - return sock - - -def _can_use_sni(): - return six.PY2 and sys.version_info >= (2, 7, 9) or sys.version_info >= (3, 2) - - -def _wrap_sni_socket(sock, sslopt, hostname, check_hostname): - context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_SSLv23)) - - if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE: - cafile = sslopt.get('ca_certs', None) - capath = sslopt.get('ca_cert_path', None) - if cafile or capath: - context.load_verify_locations(cafile=cafile, capath=capath) - elif hasattr(context, 'load_default_certs'): - context.load_default_certs(ssl.Purpose.SERVER_AUTH) - if sslopt.get('certfile', None): - context.load_cert_chain( - sslopt['certfile'], - sslopt.get('keyfile', None), - sslopt.get('password', None), - ) - # see - # https://github.com/liris/websocket-client/commit/b96a2e8fa765753e82eea531adb19716b52ca3ca#commitcomment-10803153 - context.verify_mode = sslopt['cert_reqs'] - if HAVE_CONTEXT_CHECK_HOSTNAME: - context.check_hostname = check_hostname - if 'ciphers' in sslopt: - context.set_ciphers(sslopt['ciphers']) - if 'cert_chain' in sslopt: - certfile, keyfile, password = sslopt['cert_chain'] - context.load_cert_chain(certfile, keyfile, password) - if 'ecdh_curve' in sslopt: - context.set_ecdh_curve(sslopt['ecdh_curve']) - - return context.wrap_socket( - sock, - do_handshake_on_connect=sslopt.get('do_handshake_on_connect', True), - suppress_ragged_eofs=sslopt.get('suppress_ragged_eofs', True), - server_hostname=hostname, - ) - - -def _ssl_socket(sock, user_sslopt, hostname): - sslopt = dict(cert_reqs=ssl.CERT_REQUIRED) - sslopt.update(user_sslopt) - - certPath = os.environ.get('WEBSOCKET_CLIENT_CA_BUNDLE') - if certPath and os.path.isfile(certPath) \ - and user_sslopt.get('ca_certs', None) is None \ - and user_sslopt.get('ca_cert', None) is None: - sslopt['ca_certs'] = certPath - elif certPath and os.path.isdir(certPath) \ - and user_sslopt.get('ca_cert_path', None) is None: - sslopt['ca_cert_path'] = certPath - - check_hostname = sslopt["cert_reqs"] != ssl.CERT_NONE and sslopt.pop( - 'check_hostname', True) - - if _can_use_sni(): - sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname) - else: - sslopt.pop('check_hostname', True) - sock = ssl.wrap_socket(sock, **sslopt) - - if not HAVE_CONTEXT_CHECK_HOSTNAME and check_hostname: - match_hostname(sock.getpeercert(), hostname) - - return sock - - -def _tunnel(sock, host, port, auth): - debug("Connecting proxy...") - connect_header = "CONNECT %s:%d HTTP/1.0\r\n" % (host, port) - # TODO: support digest auth. - if auth and auth[0]: - auth_str = auth[0] - if auth[1]: - auth_str += ":" + auth[1] - encoded_str = base64encode(auth_str.encode()).strip().decode().replace('\n', '') - connect_header += "Proxy-Authorization: Basic %s\r\n" % encoded_str - connect_header += "\r\n" - dump("request header", connect_header) - - send(sock, connect_header) - - try: - status, resp_headers, status_message = read_headers(sock) - except Exception as e: - raise WebSocketProxyException(str(e)) - - if status != 200: - raise WebSocketProxyException( - "failed CONNECT via proxy status: %r" % status) - - return sock - - -def read_headers(sock): - status = None - status_message = None - headers = {} - trace("--- response header ---") - - while True: - line = recv_line(sock) - line = line.decode('utf-8').strip() - if not line: - break - trace(line) - if not status: - - status_info = line.split(" ", 2) - status = int(status_info[1]) - if len(status_info) > 2: - status_message = status_info[2] - else: - kv = line.split(":", 1) - if len(kv) == 2: - key, value = kv - headers[key.lower()] = value.strip() - else: - raise WebSocketException("Invalid header") - - trace("-----------------------") - - return status, headers, status_message diff --git a/game/python-extra/websock/_logging.py b/game/python-extra/websock/_logging.py deleted file mode 100644 index c947778..0000000 --- a/game/python-extra/websock/_logging.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" -import logging - -_logger = logging.getLogger('websocket') -try: - from logging import NullHandler -except ImportError: - class NullHandler(logging.Handler): - def emit(self, record): - pass - -_logger.addHandler(NullHandler()) - -_traceEnabled = False - -__all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace", - "isEnabledForError", "isEnabledForDebug", "isEnabledForTrace"] - - -def enableTrace(traceable, handler = logging.StreamHandler()): - """ - turn on/off the traceability. - - traceable: boolean value. if set True, traceability is enabled. - """ - global _traceEnabled - _traceEnabled = traceable - if traceable: - _logger.addHandler(handler) - _logger.setLevel(logging.DEBUG) - -def dump(title, message): - if _traceEnabled: - _logger.debug("--- " + title + " ---") - _logger.debug(message) - _logger.debug("-----------------------") - - -def error(msg): - _logger.error(msg) - - -def warning(msg): - _logger.warning(msg) - - -def debug(msg): - _logger.debug(msg) - - -def trace(msg): - if _traceEnabled: - _logger.debug(msg) - - -def isEnabledForError(): - return _logger.isEnabledFor(logging.ERROR) - - -def isEnabledForDebug(): - return _logger.isEnabledFor(logging.DEBUG) - -def isEnabledForTrace(): - return _traceEnabled diff --git a/game/python-extra/websock/_socket.py b/game/python-extra/websock/_socket.py deleted file mode 100644 index 7be3913..0000000 --- a/game/python-extra/websock/_socket.py +++ /dev/null @@ -1,166 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" -import errno -import select -import socket - -import six -import sys - -from ._exceptions import * -from ._ssl_compat import * -from ._utils import * - -DEFAULT_SOCKET_OPTION = [(socket.SOL_TCP, socket.TCP_NODELAY, 1)] -if hasattr(socket, "SO_KEEPALIVE"): - DEFAULT_SOCKET_OPTION.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)) -if hasattr(socket, "TCP_KEEPIDLE"): - DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPIDLE, 30)) -if hasattr(socket, "TCP_KEEPINTVL"): - DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPINTVL, 10)) -if hasattr(socket, "TCP_KEEPCNT"): - DEFAULT_SOCKET_OPTION.append((socket.SOL_TCP, socket.TCP_KEEPCNT, 3)) - -_default_timeout = None - -__all__ = ["DEFAULT_SOCKET_OPTION", "sock_opt", "setdefaulttimeout", "getdefaulttimeout", - "recv", "recv_line", "send"] - - -class sock_opt(object): - - def __init__(self, sockopt, sslopt): - if sockopt is None: - sockopt = [] - if sslopt is None: - sslopt = {} - self.sockopt = sockopt - self.sslopt = sslopt - self.timeout = None - - -def setdefaulttimeout(timeout): - """ - Set the global timeout setting to connect. - - timeout: default socket timeout time. This value is second. - """ - global _default_timeout - _default_timeout = timeout - - -def getdefaulttimeout(): - """ - Return the global timeout setting(second) to connect. - """ - return _default_timeout - - -def recv(sock, bufsize): - if not sock: - raise WebSocketConnectionClosedException("socket is already closed.") - - def _recv(): - try: - return sock.recv(bufsize) - except SSLWantReadError: - pass - except socket.error as exc: - error_code = extract_error_code(exc) - if error_code is None: - raise - if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK: - raise - - r, w, e = select.select((sock, ), (), (), sock.gettimeout()) - if r: - return sock.recv(bufsize) - - try: - if sock.gettimeout() == 0: - bytes_ = sock.recv(bufsize) - else: - bytes_ = _recv() - except socket.timeout as e: - message = extract_err_message(e) - raise WebSocketTimeoutException(message) - except SSLError as e: - message = extract_err_message(e) - if isinstance(message, str) and 'timed out' in message: - raise WebSocketTimeoutException(message) - else: - raise - - if not bytes_: - raise WebSocketConnectionClosedException( - "Connection is already closed.") - - return bytes_ - - -def recv_line(sock): - line = [] - while True: - c = recv(sock, 1) - line.append(c) - if c == six.b("\n"): - break - return six.b("").join(line) - - -def send(sock, data): - if isinstance(data, six.text_type): - data = data.encode('utf-8') - - if not sock: - raise WebSocketConnectionClosedException("socket is already closed.") - - def _send(): - try: - return sock.send(data) - except SSLWantWriteError: - pass - except socket.error as exc: - error_code = extract_error_code(exc) - if error_code is None: - raise - if error_code != errno.EAGAIN or error_code != errno.EWOULDBLOCK: - raise - - r, w, e = select.select((), (sock, ), (), sock.gettimeout()) - if w: - return sock.send(data) - - try: - if sock.gettimeout() == 0: - return sock.send(data) - else: - return _send() - except socket.timeout as e: - message = extract_err_message(e) - raise WebSocketTimeoutException(message) - except Exception as e: - message = extract_err_message(e) - if isinstance(message, str) and "timed out" in message: - raise WebSocketTimeoutException(message) - else: - raise diff --git a/game/python-extra/websock/_ssl_compat.py b/game/python-extra/websock/_ssl_compat.py deleted file mode 100644 index 96cd173..0000000 --- a/game/python-extra/websock/_ssl_compat.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" -__all__ = ["HAVE_SSL", "ssl", "SSLError", "SSLWantReadError", "SSLWantWriteError"] - -try: - import ssl - from ssl import SSLError - from ssl import SSLWantReadError - from ssl import SSLWantWriteError - if hasattr(ssl, 'SSLContext') and hasattr(ssl.SSLContext, 'check_hostname'): - HAVE_CONTEXT_CHECK_HOSTNAME = True - else: - HAVE_CONTEXT_CHECK_HOSTNAME = False - if hasattr(ssl, "match_hostname"): - from ssl import match_hostname - else: - from backports.ssl_match_hostname import match_hostname - __all__.append("match_hostname") - __all__.append("HAVE_CONTEXT_CHECK_HOSTNAME") - - HAVE_SSL = True -except ImportError: - # dummy class of SSLError for ssl none-support environment. - class SSLError(Exception): - pass - - class SSLWantReadError(Exception): - pass - - class SSLWantWriteError(Exception): - pass - - ssl = lambda: None - - HAVE_SSL = False diff --git a/game/python-extra/websock/_url.py b/game/python-extra/websock/_url.py deleted file mode 100644 index a394fc3..0000000 --- a/game/python-extra/websock/_url.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" - -import os -import socket -import struct - -from six.moves.urllib.parse import urlparse - - -__all__ = ["parse_url", "get_proxy_info"] - - -def parse_url(url): - """ - parse url and the result is tuple of - (hostname, port, resource path and the flag of secure mode) - - url: url string. - """ - if ":" not in url: - raise ValueError("url is invalid") - - scheme, url = url.split(":", 1) - - parsed = urlparse(url, scheme="ws") - if parsed.hostname: - hostname = parsed.hostname - else: - raise ValueError("hostname is invalid") - port = 0 - if parsed.port: - port = parsed.port - - is_secure = False - if scheme == "ws": - if not port: - port = 80 - elif scheme == "wss": - is_secure = True - if not port: - port = 443 - else: - raise ValueError("scheme %s is invalid" % scheme) - - if parsed.path: - resource = parsed.path - else: - resource = "/" - - if parsed.query: - resource += "?" + parsed.query - - return hostname, port, resource, is_secure - - -DEFAULT_NO_PROXY_HOST = ["localhost", "127.0.0.1"] - - -def _is_ip_address(addr): - try: - socket.inet_aton(addr) - except socket.error: - return False - else: - return True - - -def _is_subnet_address(hostname): - try: - addr, netmask = hostname.split("/") - return _is_ip_address(addr) and 0 <= int(netmask) < 32 - except ValueError: - return False - - -def _is_address_in_network(ip, net): - ipaddr = struct.unpack('I', socket.inet_aton(ip))[0] - netaddr, bits = net.split('/') - netmask = struct.unpack('I', socket.inet_aton(netaddr))[0] & ((2 << int(bits) - 1) - 1) - return ipaddr & netmask == netmask - - -def _is_no_proxy_host(hostname, no_proxy): - if not no_proxy: - v = os.environ.get("no_proxy", "").replace(" ", "") - if v: - no_proxy = v.split(",") - if not no_proxy: - no_proxy = DEFAULT_NO_PROXY_HOST - - if hostname in no_proxy: - return True - elif _is_ip_address(hostname): - return any([_is_address_in_network(hostname, subnet) for subnet in no_proxy if _is_subnet_address(subnet)]) - - return False - - -def get_proxy_info( - hostname, is_secure, proxy_host=None, proxy_port=0, proxy_auth=None, - no_proxy=None, proxy_type='http'): - """ - try to retrieve proxy host and port from environment - if not provided in options. - result is (proxy_host, proxy_port, proxy_auth). - proxy_auth is tuple of username and password - of proxy authentication information. - - hostname: websocket server name. - - is_secure: is the connection secure? (wss) - looks for "https_proxy" in env - before falling back to "http_proxy" - - options: "http_proxy_host" - http proxy host name. - "http_proxy_port" - http proxy port. - "http_no_proxy" - host names, which doesn't use proxy. - "http_proxy_auth" - http proxy auth information. - tuple of username and password. - default is None - "proxy_type" - if set to "socks5" PySocks wrapper - will be used in place of a http proxy. - default is "http" - """ - if _is_no_proxy_host(hostname, no_proxy): - return None, 0, None - - if proxy_host: - port = proxy_port - auth = proxy_auth - return proxy_host, port, auth - - env_keys = ["http_proxy"] - if is_secure: - env_keys.insert(0, "https_proxy") - - for key in env_keys: - value = os.environ.get(key, None) - if value: - proxy = urlparse(value) - auth = (proxy.username, proxy.password) if proxy.username else None - return proxy.hostname, proxy.port, auth - - return None, 0, None diff --git a/game/python-extra/websock/_utils.py b/game/python-extra/websock/_utils.py deleted file mode 100644 index 32ee12e..0000000 --- a/game/python-extra/websock/_utils.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -websocket - WebSocket client library for Python - -Copyright (C) 2010 Hiroki Ohtani(liris) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1335 USA - -""" -import six - -__all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code"] - - -class NoLock(object): - - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_value, traceback): - pass - - -try: - # If wsaccel is available we use compiled routines to validate UTF-8 - # strings. - from wsaccel.utf8validator import Utf8Validator - - def _validate_utf8(utfbytes): - return Utf8Validator().validate(utfbytes)[0] - -except ImportError: - # UTF-8 validator - # python implementation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ - - _UTF8_ACCEPT = 0 - _UTF8_REJECT = 12 - - _UTF8D = [ - # The first part of the table maps bytes to character classes that - # to reduce the size of the transition table and create bitmasks. - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, - 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, - - # The second part is a transition table that maps a combination - # of a state of the automaton and a character class to a state. - 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, - 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, - 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, - 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, - 12,36,12,12,12,12,12,12,12,12,12,12, ] - - def _decode(state, codep, ch): - tp = _UTF8D[ch] - - codep = (ch & 0x3f) | (codep << 6) if ( - state != _UTF8_ACCEPT) else (0xff >> tp) & ch - state = _UTF8D[256 + state + tp] - - return state, codep - - def _validate_utf8(utfbytes): - state = _UTF8_ACCEPT - codep = 0 - for i in utfbytes: - if six.PY2: - i = ord(i) - state, codep = _decode(state, codep, i) - if state == _UTF8_REJECT: - return False - - return True - - -def validate_utf8(utfbytes): - """ - validate utf8 byte string. - utfbytes: utf byte string to check. - return value: if valid utf8 string, return true. Otherwise, return false. - """ - return _validate_utf8(utfbytes) - - -def extract_err_message(exception): - if exception.args: - return exception.args[0] - else: - return None - - -def extract_error_code(exception): - if exception.args and len(exception.args) > 1: - return exception.args[0] if isinstance(exception.args[0], int) else None diff --git a/game/python-extra/websock/tests/data/header01.txt b/game/python-extra/websock/tests/data/header01.txt deleted file mode 100644 index 3142b43..0000000 --- a/game/python-extra/websock/tests/data/header01.txt +++ /dev/null @@ -1,6 +0,0 @@ -HTTP/1.1 101 WebSocket Protocol Handshake
-Connection: Upgrade
-Upgrade: WebSocket
-Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0=
-some_header: something
-
diff --git a/game/python-extra/websock/tests/data/header02.txt b/game/python-extra/websock/tests/data/header02.txt deleted file mode 100644 index a9dd2ce..0000000 --- a/game/python-extra/websock/tests/data/header02.txt +++ /dev/null @@ -1,6 +0,0 @@ -HTTP/1.1 101 WebSocket Protocol Handshake
-Connection: Upgrade
-Upgrade WebSocket
-Sec-WebSocket-Accept: Kxep+hNu9n51529fGidYu7a3wO0=
-some_header: something
-
diff --git a/game/python-extra/websock/tests/test_cookiejar.py b/game/python-extra/websock/tests/test_cookiejar.py deleted file mode 100644 index c40a00b..0000000 --- a/game/python-extra/websock/tests/test_cookiejar.py +++ /dev/null @@ -1,98 +0,0 @@ -import unittest - -from websocket._cookiejar import SimpleCookieJar - -try: - import Cookie -except: - import http.cookies as Cookie - - -class CookieJarTest(unittest.TestCase): - def testAdd(self): - cookie_jar = SimpleCookieJar() - cookie_jar.add("") - self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") - - cookie_jar = SimpleCookieJar() - cookie_jar.add("a=b") - self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") - - cookie_jar = SimpleCookieJar() - cookie_jar.add("a=b; domain=.abc") - self.assertTrue(".abc" in cookie_jar.jar) - - cookie_jar = SimpleCookieJar() - cookie_jar.add("a=b; domain=abc") - self.assertTrue(".abc" in cookie_jar.jar) - self.assertTrue("abc" not in cookie_jar.jar) - - cookie_jar = SimpleCookieJar() - cookie_jar.add("a=b; c=d; domain=abc") - self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") - - cookie_jar = SimpleCookieJar() - cookie_jar.add("a=b; c=d; domain=abc") - cookie_jar.add("e=f; domain=abc") - self.assertEquals(cookie_jar.get("abc"), "a=b; c=d; e=f") - - cookie_jar = SimpleCookieJar() - cookie_jar.add("a=b; c=d; domain=abc") - cookie_jar.add("e=f; domain=.abc") - self.assertEquals(cookie_jar.get("abc"), "a=b; c=d; e=f") - - cookie_jar = SimpleCookieJar() - cookie_jar.add("a=b; c=d; domain=abc") - cookie_jar.add("e=f; domain=xyz") - self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") - self.assertEquals(cookie_jar.get("xyz"), "e=f") - self.assertEquals(cookie_jar.get("something"), "") - - def testSet(self): - cookie_jar = SimpleCookieJar() - cookie_jar.set("a=b") - self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") - - cookie_jar = SimpleCookieJar() - cookie_jar.set("a=b; domain=.abc") - self.assertTrue(".abc" in cookie_jar.jar) - - cookie_jar = SimpleCookieJar() - cookie_jar.set("a=b; domain=abc") - self.assertTrue(".abc" in cookie_jar.jar) - self.assertTrue("abc" not in cookie_jar.jar) - - cookie_jar = SimpleCookieJar() - cookie_jar.set("a=b; c=d; domain=abc") - self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") - - cookie_jar = SimpleCookieJar() - cookie_jar.set("a=b; c=d; domain=abc") - cookie_jar.set("e=f; domain=abc") - self.assertEquals(cookie_jar.get("abc"), "e=f") - - cookie_jar = SimpleCookieJar() - cookie_jar.set("a=b; c=d; domain=abc") - cookie_jar.set("e=f; domain=.abc") - self.assertEquals(cookie_jar.get("abc"), "e=f") - - cookie_jar = SimpleCookieJar() - cookie_jar.set("a=b; c=d; domain=abc") - cookie_jar.set("e=f; domain=xyz") - self.assertEquals(cookie_jar.get("abc"), "a=b; c=d") - self.assertEquals(cookie_jar.get("xyz"), "e=f") - self.assertEquals(cookie_jar.get("something"), "") - - def testGet(self): - cookie_jar = SimpleCookieJar() - cookie_jar.set("a=b; c=d; domain=abc.com") - self.assertEquals(cookie_jar.get("abc.com"), "a=b; c=d") - self.assertEquals(cookie_jar.get("x.abc.com"), "a=b; c=d") - self.assertEquals(cookie_jar.get("abc.com.es"), "") - self.assertEquals(cookie_jar.get("xabc.com"), "") - - cookie_jar.set("a=b; c=d; domain=.abc.com") - self.assertEquals(cookie_jar.get("abc.com"), "a=b; c=d") - self.assertEquals(cookie_jar.get("x.abc.com"), "a=b; c=d") - self.assertEquals(cookie_jar.get("abc.com.es"), "") - self.assertEquals(cookie_jar.get("xabc.com"), "") diff --git a/game/python-extra/websock/tests/test_websocket.py b/game/python-extra/websock/tests/test_websocket.py deleted file mode 100644 index 8b131bb..0000000 --- a/game/python-extra/websock/tests/test_websocket.py +++ /dev/null @@ -1,665 +0,0 @@ -# -*- coding: utf-8 -*- -# - -import sys -sys.path[0:0] = [""] - -import os -import os.path -import socket - -import six - -# websocket-client -import websocket as ws -from websocket._handshake import _create_sec_websocket_key, \ - _validate as _validate_header -from websocket._http import read_headers -from websocket._url import get_proxy_info, parse_url -from websocket._utils import validate_utf8 - -if six.PY3: - from base64 import decodebytes as base64decode -else: - from base64 import decodestring as base64decode - -if sys.version_info[0] == 2 and sys.version_info[1] < 7: - import unittest2 as unittest -else: - import unittest - -try: - from ssl import SSLError -except ImportError: - # dummy class of SSLError for ssl none-support environment. - class SSLError(Exception): - pass - -# Skip test to access the internet. -TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' - -# Skip Secure WebSocket test. -TEST_SECURE_WS = True -TRACEABLE = True - - -def create_mask_key(_): - return "abcd" - - -class SockMock(object): - def __init__(self): - self.data = [] - self.sent = [] - - def add_packet(self, data): - self.data.append(data) - - def gettimeout(self): - return None - - def recv(self, bufsize): - if self.data: - e = self.data.pop(0) - if isinstance(e, Exception): - raise e - if len(e) > bufsize: - self.data.insert(0, e[bufsize:]) - return e[:bufsize] - - def send(self, data): - self.sent.append(data) - return len(data) - - def close(self): - pass - - -class HeaderSockMock(SockMock): - - def __init__(self, fname): - SockMock.__init__(self) - path = os.path.join(os.path.dirname(__file__), fname) - with open(path, "rb") as f: - self.add_packet(f.read()) - - -class WebSocketTest(unittest.TestCase): - def setUp(self): - ws.enableTrace(TRACEABLE) - - def tearDown(self): - pass - - def testDefaultTimeout(self): - self.assertEqual(ws.getdefaulttimeout(), None) - ws.setdefaulttimeout(10) - self.assertEqual(ws.getdefaulttimeout(), 10) - ws.setdefaulttimeout(None) - - def testParseUrl(self): - p = parse_url("ws://www.example.com/r") - self.assertEqual(p[0], "www.example.com") - self.assertEqual(p[1], 80) - self.assertEqual(p[2], "/r") - self.assertEqual(p[3], False) - - p = parse_url("ws://www.example.com/r/") - self.assertEqual(p[0], "www.example.com") - self.assertEqual(p[1], 80) - self.assertEqual(p[2], "/r/") - self.assertEqual(p[3], False) - - p = parse_url("ws://www.example.com/") - self.assertEqual(p[0], "www.example.com") - self.assertEqual(p[1], 80) - self.assertEqual(p[2], "/") - self.assertEqual(p[3], False) - - p = parse_url("ws://www.example.com") - self.assertEqual(p[0], "www.example.com") - self.assertEqual(p[1], 80) - self.assertEqual(p[2], "/") - self.assertEqual(p[3], False) - - p = parse_url("ws://www.example.com:8080/r") - self.assertEqual(p[0], "www.example.com") - self.assertEqual(p[1], 8080) - self.assertEqual(p[2], "/r") - self.assertEqual(p[3], False) - - p = parse_url("ws://www.example.com:8080/") - self.assertEqual(p[0], "www.example.com") - self.assertEqual(p[1], 8080) - self.assertEqual(p[2], "/") - self.assertEqual(p[3], False) - - p = parse_url("ws://www.example.com:8080") - self.assertEqual(p[0], "www.example.com") - self.assertEqual(p[1], 8080) - self.assertEqual(p[2], "/") - self.assertEqual(p[3], False) - - p = parse_url("wss://www.example.com:8080/r") - self.assertEqual(p[0], "www.example.com") - self.assertEqual(p[1], 8080) - self.assertEqual(p[2], "/r") - self.assertEqual(p[3], True) - - p = parse_url("wss://www.example.com:8080/r?key=value") - self.assertEqual(p[0], "www.example.com") - self.assertEqual(p[1], 8080) - self.assertEqual(p[2], "/r?key=value") - self.assertEqual(p[3], True) - - self.assertRaises(ValueError, parse_url, "http://www.example.com/r") - - if sys.version_info[0] == 2 and sys.version_info[1] < 7: - return - - p = parse_url("ws://[2a03:4000:123:83::3]/r") - self.assertEqual(p[0], "2a03:4000:123:83::3") - self.assertEqual(p[1], 80) - self.assertEqual(p[2], "/r") - self.assertEqual(p[3], False) - - p = parse_url("ws://[2a03:4000:123:83::3]:8080/r") - self.assertEqual(p[0], "2a03:4000:123:83::3") - self.assertEqual(p[1], 8080) - self.assertEqual(p[2], "/r") - self.assertEqual(p[3], False) - - p = parse_url("wss://[2a03:4000:123:83::3]/r") - self.assertEqual(p[0], "2a03:4000:123:83::3") - self.assertEqual(p[1], 443) - self.assertEqual(p[2], "/r") - self.assertEqual(p[3], True) - - p = parse_url("wss://[2a03:4000:123:83::3]:8080/r") - self.assertEqual(p[0], "2a03:4000:123:83::3") - self.assertEqual(p[1], 8080) - self.assertEqual(p[2], "/r") - self.assertEqual(p[3], True) - - def testWSKey(self): - key = _create_sec_websocket_key() - self.assertTrue(key != 24) - self.assertTrue(six.u("¥n") not in key) - - def testWsUtils(self): - key = "c6b8hTg4EeGb2gQMztV1/g==" - required_header = { - "upgrade": "websocket", - "connection": "upgrade", - "sec-websocket-accept": "Kxep+hNu9n51529fGidYu7a3wO0=", - } - self.assertEqual(_validate_header(required_header, key, None), (True, None)) - - header = required_header.copy() - header["upgrade"] = "http" - self.assertEqual(_validate_header(header, key, None), (False, None)) - del header["upgrade"] - self.assertEqual(_validate_header(header, key, None), (False, None)) - - header = required_header.copy() - header["connection"] = "something" - self.assertEqual(_validate_header(header, key, None), (False, None)) - del header["connection"] - self.assertEqual(_validate_header(header, key, None), (False, None)) - - header = required_header.copy() - header["sec-websocket-accept"] = "something" - self.assertEqual(_validate_header(header, key, None), (False, None)) - del header["sec-websocket-accept"] - self.assertEqual(_validate_header(header, key, None), (False, None)) - - header = required_header.copy() - header["sec-websocket-protocol"] = "sub1" - self.assertEqual(_validate_header(header, key, ["sub1", "sub2"]), (True, "sub1")) - self.assertEqual(_validate_header(header, key, ["sub2", "sub3"]), (False, None)) - - header = required_header.copy() - header["sec-websocket-protocol"] = "sUb1" - self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (True, "sub1")) - - - def testReadHeader(self): - status, header, status_message = read_headers(HeaderSockMock("data/header01.txt")) - self.assertEqual(status, 101) - self.assertEqual(header["connection"], "Upgrade") - - HeaderSockMock("data/header02.txt") - self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt")) - - def testSend(self): - # TODO: add longer frame data - sock = ws.WebSocket() - sock.set_mask_key(create_mask_key) - s = sock.sock = HeaderSockMock("data/header01.txt") - sock.send("Hello") - self.assertEqual(s.sent[0], six.b("\x81\x85abcd)\x07\x0f\x08\x0e")) - - sock.send("こんにちは") - self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")) - - sock.send(u"こんにちは") - self.assertEqual(s.sent[1], six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc")) - - sock.send("x" * 127) - - def testRecv(self): - # TODO: add longer frame data - sock = ws.WebSocket() - s = sock.sock = SockMock() - something = six.b("\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc") - s.add_packet(something) - data = sock.recv() - self.assertEqual(data, "こんにちは") - - s.add_packet(six.b("\x81\x85abcd)\x07\x0f\x08\x0e")) - data = sock.recv() - self.assertEqual(data, "Hello") - - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - def testIter(self): - count = 2 - for _ in ws.create_connection('ws://stream.meetup.com/2/rsvps'): - count -= 1 - if count == 0: - break - - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - def testNext(self): - sock = ws.create_connection('ws://stream.meetup.com/2/rsvps') - self.assertEqual(str, type(next(sock))) - - def testInternalRecvStrict(self): - sock = ws.WebSocket() - s = sock.sock = SockMock() - s.add_packet(six.b("foo")) - s.add_packet(socket.timeout()) - s.add_packet(six.b("bar")) - # s.add_packet(SSLError("The read operation timed out")) - s.add_packet(six.b("baz")) - with self.assertRaises(ws.WebSocketTimeoutException): - sock.frame_buffer.recv_strict(9) - # if six.PY2: - # with self.assertRaises(ws.WebSocketTimeoutException): - # data = sock._recv_strict(9) - # else: - # with self.assertRaises(SSLError): - # data = sock._recv_strict(9) - data = sock.frame_buffer.recv_strict(9) - self.assertEqual(data, six.b("foobarbaz")) - with self.assertRaises(ws.WebSocketConnectionClosedException): - sock.frame_buffer.recv_strict(1) - - def testRecvTimeout(self): - sock = ws.WebSocket() - s = sock.sock = SockMock() - s.add_packet(six.b("\x81")) - s.add_packet(socket.timeout()) - s.add_packet(six.b("\x8dabcd\x29\x07\x0f\x08\x0e")) - s.add_packet(socket.timeout()) - s.add_packet(six.b("\x4e\x43\x33\x0e\x10\x0f\x00\x40")) - with self.assertRaises(ws.WebSocketTimeoutException): - sock.recv() - with self.assertRaises(ws.WebSocketTimeoutException): - sock.recv() - data = sock.recv() - self.assertEqual(data, "Hello, World!") - with self.assertRaises(ws.WebSocketConnectionClosedException): - sock.recv() - - def testRecvWithSimpleFragmentation(self): - sock = ws.WebSocket() - s = sock.sock = SockMock() - # OPCODE=TEXT, FIN=0, MSG="Brevity is " - s.add_packet(six.b("\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) - # OPCODE=CONT, FIN=1, MSG="the soul of wit" - s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) - data = sock.recv() - self.assertEqual(data, "Brevity is the soul of wit") - with self.assertRaises(ws.WebSocketConnectionClosedException): - sock.recv() - - def testRecvWithFireEventOfFragmentation(self): - sock = ws.WebSocket(fire_cont_frame=True) - s = sock.sock = SockMock() - # OPCODE=TEXT, FIN=0, MSG="Brevity is " - s.add_packet(six.b("\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) - # OPCODE=CONT, FIN=0, MSG="Brevity is " - s.add_packet(six.b("\x00\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) - # OPCODE=CONT, FIN=1, MSG="the soul of wit" - s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) - - _, data = sock.recv_data() - self.assertEqual(data, six.b("Brevity is ")) - _, data = sock.recv_data() - self.assertEqual(data, six.b("Brevity is ")) - _, data = sock.recv_data() - self.assertEqual(data, six.b("the soul of wit")) - - # OPCODE=CONT, FIN=0, MSG="Brevity is " - s.add_packet(six.b("\x80\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C")) - - with self.assertRaises(ws.WebSocketException): - sock.recv_data() - - with self.assertRaises(ws.WebSocketConnectionClosedException): - sock.recv() - - def testClose(self): - sock = ws.WebSocket() - sock.sock = SockMock() - sock.connected = True - sock.close() - self.assertEqual(sock.connected, False) - - sock = ws.WebSocket() - s = sock.sock = SockMock() - sock.connected = True - s.add_packet(six.b('\x88\x80\x17\x98p\x84')) - sock.recv() - self.assertEqual(sock.connected, False) - - def testRecvContFragmentation(self): - sock = ws.WebSocket() - s = sock.sock = SockMock() - # OPCODE=CONT, FIN=1, MSG="the soul of wit" - s.add_packet(six.b("\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17")) - self.assertRaises(ws.WebSocketException, sock.recv) - - def testRecvWithProlongedFragmentation(self): - sock = ws.WebSocket() - s = sock.sock = SockMock() - # OPCODE=TEXT, FIN=0, MSG="Once more unto the breach, " - s.add_packet(six.b("\x01\x9babcd.\x0c\x00\x01A\x0f\x0c\x16\x04B\x16\n\x15" - "\rC\x10\t\x07C\x06\x13\x07\x02\x07\tNC")) - # OPCODE=CONT, FIN=0, MSG="dear friends, " - s.add_packet(six.b("\x00\x8eabcd\x05\x07\x02\x16A\x04\x11\r\x04\x0c\x07" - "\x17MB")) - # OPCODE=CONT, FIN=1, MSG="once more" - s.add_packet(six.b("\x80\x89abcd\x0e\x0c\x00\x01A\x0f\x0c\x16\x04")) - data = sock.recv() - self.assertEqual( - data, - "Once more unto the breach, dear friends, once more") - with self.assertRaises(ws.WebSocketConnectionClosedException): - sock.recv() - - def testRecvWithFragmentationAndControlFrame(self): - sock = ws.WebSocket() - sock.set_mask_key(create_mask_key) - s = sock.sock = SockMock() - # OPCODE=TEXT, FIN=0, MSG="Too much " - s.add_packet(six.b("\x01\x89abcd5\r\x0cD\x0c\x17\x00\x0cA")) - # OPCODE=PING, FIN=1, MSG="Please PONG this" - s.add_packet(six.b("\x89\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17")) - # OPCODE=CONT, FIN=1, MSG="of a good thing" - s.add_packet(six.b("\x80\x8fabcd\x0e\x04C\x05A\x05\x0c\x0b\x05B\x17\x0c" - "\x08\x0c\x04")) - data = sock.recv() - self.assertEqual(data, "Too much of a good thing") - with self.assertRaises(ws.WebSocketConnectionClosedException): - sock.recv() - self.assertEqual( - s.sent[0], - six.b("\x8a\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17")) - - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - def testWebSocket(self): - s = ws.create_connection("ws://echo.websocket.org/") - self.assertNotEqual(s, None) - s.send("Hello, World") - result = s.recv() - self.assertEqual(result, "Hello, World") - - s.send(u"こにゃにゃちは、世界") - result = s.recv() - self.assertEqual(result, "こにゃにゃちは、世界") - s.close() - - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - def testPingPong(self): - s = ws.create_connection("ws://echo.websocket.org/") - self.assertNotEqual(s, None) - s.ping("Hello") - s.pong("Hi") - s.close() - - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - @unittest.skipUnless(TEST_SECURE_WS, "wss://echo.websocket.org doesn't work well.") - def testSecureWebSocket(self): - if 1: - import ssl - s = ws.create_connection("wss://echo.websocket.org/") - self.assertNotEqual(s, None) - self.assertTrue(isinstance(s.sock, ssl.SSLSocket)) - s.send("Hello, World") - result = s.recv() - self.assertEqual(result, "Hello, World") - s.send(u"こにゃにゃちは、世界") - result = s.recv() - self.assertEqual(result, "こにゃにゃちは、世界") - s.close() - #except: - # pass - - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - def testWebSocketWihtCustomHeader(self): - s = ws.create_connection("ws://echo.websocket.org/", - headers={"User-Agent": "PythonWebsocketClient"}) - self.assertNotEqual(s, None) - s.send("Hello, World") - result = s.recv() - self.assertEqual(result, "Hello, World") - s.close() - - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - def testAfterClose(self): - s = ws.create_connection("ws://echo.websocket.org/") - self.assertNotEqual(s, None) - s.close() - self.assertRaises(ws.WebSocketConnectionClosedException, s.send, "Hello") - self.assertRaises(ws.WebSocketConnectionClosedException, s.recv) - - def testNonce(self): - """ WebSocket key should be a random 16-byte nonce. - """ - key = _create_sec_websocket_key() - nonce = base64decode(key.encode("utf-8")) - self.assertEqual(16, len(nonce)) - - -class WebSocketAppTest(unittest.TestCase): - - class NotSetYet(object): - """ A marker class for signalling that a value hasn't been set yet. - """ - - def setUp(self): - ws.enableTrace(TRACEABLE) - - WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet() - WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() - WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() - - def tearDown(self): - WebSocketAppTest.keep_running_open = WebSocketAppTest.NotSetYet() - WebSocketAppTest.keep_running_close = WebSocketAppTest.NotSetYet() - WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() - - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - def testKeepRunning(self): - """ A WebSocketApp should keep running as long as its self.keep_running - is not False (in the boolean context). - """ - - def on_open(self, *args, **kwargs): - """ Set the keep_running flag for later inspection and immediately - close the connection. - """ - WebSocketAppTest.keep_running_open = self.keep_running - - self.close() - - def on_close(self, *args, **kwargs): - """ Set the keep_running flag for the test to use. - """ - WebSocketAppTest.keep_running_close = self.keep_running - - app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, on_close=on_close) - app.run_forever() - - # if numpy is installed, this assertion fail - # self.assertFalse(isinstance(WebSocketAppTest.keep_running_open, - # WebSocketAppTest.NotSetYet)) - - # self.assertFalse(isinstance(WebSocketAppTest.keep_running_close, - # WebSocketAppTest.NotSetYet)) - - # self.assertEqual(True, WebSocketAppTest.keep_running_open) - # self.assertEqual(False, WebSocketAppTest.keep_running_close) - - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - def testSockMaskKey(self): - """ A WebSocketApp should forward the received mask_key function down - to the actual socket. - """ - - def my_mask_key_func(): - pass - - def on_open(self, *args, **kwargs): - """ Set the value so the test can use it later on and immediately - close the connection. - """ - WebSocketAppTest.get_mask_key_id = id(self.get_mask_key) - self.close() - - app = ws.WebSocketApp('ws://echo.websocket.org/', on_open=on_open, get_mask_key=my_mask_key_func) - app.run_forever() - - # if numpu is installed, this assertion fail - # Note: We can't use 'is' for comparing the functions directly, need to use 'id'. - # self.assertEqual(WebSocketAppTest.get_mask_key_id, id(my_mask_key_func)) - - -class SockOptTest(unittest.TestCase): - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - def testSockOpt(self): - sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),) - s = ws.create_connection("ws://echo.websocket.org", sockopt=sockopt) - self.assertNotEqual(s.sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY), 0) - s.close() - - -class UtilsTest(unittest.TestCase): - def testUtf8Validator(self): - state = validate_utf8(six.b('\xf0\x90\x80\x80')) - self.assertEqual(state, True) - state = validate_utf8(six.b('\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\xed\xa0\x80edited')) - self.assertEqual(state, False) - state = validate_utf8(six.b('')) - self.assertEqual(state, True) - - -class ProxyInfoTest(unittest.TestCase): - def setUp(self): - self.http_proxy = os.environ.get("http_proxy", None) - self.https_proxy = os.environ.get("https_proxy", None) - if "http_proxy" in os.environ: - del os.environ["http_proxy"] - if "https_proxy" in os.environ: - del os.environ["https_proxy"] - - def tearDown(self): - if self.http_proxy: - os.environ["http_proxy"] = self.http_proxy - elif "http_proxy" in os.environ: - del os.environ["http_proxy"] - - if self.https_proxy: - os.environ["https_proxy"] = self.https_proxy - elif "https_proxy" in os.environ: - del os.environ["https_proxy"] - - def testProxyFromArgs(self): - self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost"), ("localhost", 0, None)) - self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None)) - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost"), ("localhost", 0, None)) - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128), ("localhost", 3128, None)) - - self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_auth=("a", "b")), - ("localhost", 0, ("a", "b"))) - self.assertEqual(get_proxy_info("echo.websocket.org", False, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), - ("localhost", 3128, ("a", "b"))) - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_auth=("a", "b")), - ("localhost", 0, ("a", "b"))) - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), - ("localhost", 3128, ("a", "b"))) - - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, no_proxy=["example.com"], proxy_auth=("a", "b")), - ("localhost", 3128, ("a", "b"))) - self.assertEqual(get_proxy_info("echo.websocket.org", True, proxy_host="localhost", proxy_port=3128, no_proxy=["echo.websocket.org"], proxy_auth=("a", "b")), - (None, 0, None)) - - def testProxyFromEnv(self): - os.environ["http_proxy"] = "http://localhost/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None)) - os.environ["http_proxy"] = "http://localhost:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None)) - - os.environ["http_proxy"] = "http://localhost/" - os.environ["https_proxy"] = "http://localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, None)) - os.environ["http_proxy"] = "http://localhost:3128/" - os.environ["https_proxy"] = "http://localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, None)) - - os.environ["http_proxy"] = "http://localhost/" - os.environ["https_proxy"] = "http://localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, None)) - os.environ["http_proxy"] = "http://localhost:3128/" - os.environ["https_proxy"] = "http://localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, None)) - - - os.environ["http_proxy"] = "http://a:b@localhost/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b"))) - os.environ["http_proxy"] = "http://a:b@localhost:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b"))) - - os.environ["http_proxy"] = "http://a:b@localhost/" - os.environ["https_proxy"] = "http://a:b@localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", None, ("a", "b"))) - os.environ["http_proxy"] = "http://a:b@localhost:3128/" - os.environ["https_proxy"] = "http://a:b@localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", False), ("localhost", 3128, ("a", "b"))) - - os.environ["http_proxy"] = "http://a:b@localhost/" - os.environ["https_proxy"] = "http://a:b@localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", None, ("a", "b"))) - os.environ["http_proxy"] = "http://a:b@localhost:3128/" - os.environ["https_proxy"] = "http://a:b@localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.org", True), ("localhost2", 3128, ("a", "b"))) - - os.environ["http_proxy"] = "http://a:b@localhost/" - os.environ["https_proxy"] = "http://a:b@localhost2/" - os.environ["no_proxy"] = "example1.com,example2.com" - self.assertEqual(get_proxy_info("example.1.com", True), ("localhost2", None, ("a", "b"))) - os.environ["http_proxy"] = "http://a:b@localhost:3128/" - os.environ["https_proxy"] = "http://a:b@localhost2:3128/" - os.environ["no_proxy"] = "example1.com,example2.com, echo.websocket.org" - self.assertEqual(get_proxy_info("echo.websocket.org", True), (None, 0, None)) - - os.environ["http_proxy"] = "http://a:b@localhost:3128/" - os.environ["https_proxy"] = "http://a:b@localhost2:3128/" - os.environ["no_proxy"] = "127.0.0.0/8, 192.168.0.0/16" - self.assertEqual(get_proxy_info("127.0.0.1", False), (None, 0, None)) - self.assertEqual(get_proxy_info("192.168.1.1", False), (None, 0, None)) - - -if __name__ == "__main__": - unittest.main() diff --git a/game/python-extra/ws4py/__init__.py b/game/python-extra/ws4py/__init__.py new file mode 100644 index 0000000..1f618b0 --- /dev/null +++ b/game/python-extra/ws4py/__init__.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# +# 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 ws4py 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 +# OWNER 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. +# +import logging +#import logging.handlers as handlers + +__author__ = "Sylvain Hellegouarch" +__version__ = "0.5.1" +__all__ = ['WS_KEY', 'WS_VERSION', 'configure_logger', 'format_addresses'] + +WS_KEY = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" +WS_VERSION = (8, 13) + +def configure_logger(stdout=True, filepath=None, level=logging.INFO): + logger = logging.getLogger('ws4py') + logger.setLevel(level) + logfmt = logging.Formatter("[%(asctime)s] %(levelname)s %(message)s") + + ######### + # @TMW2 + #if filepath: + # h = handlers.RotatingFileHandler(filepath, maxBytes=10485760, backupCount=3) + # h.setLevel(level) + # h.setFormatter(logfmt) + # logger.addHandler(h) + # @TMW2 + ######### + + if stdout: + import sys + h = logging.StreamHandler(sys.stdout) + h.setLevel(level) + h.setFormatter(logfmt) + logger.addHandler(h) + + return logger + +def format_addresses(ws): + me = ws.local_address + peer = ws.peer_address + if isinstance(me, tuple) and isinstance(peer, tuple): + me_ip, me_port = ws.local_address + peer_ip, peer_port = ws.peer_address + return "[Local => %s:%d | Remote => %s:%d]" % (me_ip, me_port, peer_ip, peer_port) + + return "[Bound to '%s']" % me diff --git a/game/python-extra/ws4py/async_websocket.py b/game/python-extra/ws4py/async_websocket.py new file mode 100644 index 0000000..9e2a4c7 --- /dev/null +++ b/game/python-extra/ws4py/async_websocket.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +__doc__ = """ +WebSocket implementation that relies on two new Python +features: + +* asyncio to provide the high-level interface above transports +* yield from to delegate to the reading stream whenever more + bytes are required + +You can use these implementations in that context +and benefit from those features whilst using ws4py. + +Strictly speaking this module probably doesn't have to +be called async_websocket but it feels this will be its typical +usage and is probably more readable than +delegated_generator_websocket_on_top_of_asyncio.py +""" +import asyncio +import types + +from ws4py.websocket import WebSocket as _WebSocket +from ws4py.messaging import Message + +__all__ = ['WebSocket', 'EchoWebSocket'] + +class WebSocket(_WebSocket): + def __init__(self, proto): + """ + A :pep:`3156` ready websocket handler that works + well in a coroutine-aware loop such as the one provided + by the asyncio module. + + The provided `proto` instance is a + :class:`asyncio.Protocol` subclass instance that will + be used internally to read and write from the + underlying transport. + + Because the base :class:`ws4py.websocket.WebSocket` + class is still coupled a bit to the socket interface, + we have to override a little more than necessary + to play nice with the :pep:`3156` interface. Hopefully, + some day this will be cleaned out. + """ + _WebSocket.__init__(self, None) + self.started = False + self.proto = proto + + @property + def local_address(self): + """ + Local endpoint address as a tuple + """ + if not self._local_address: + self._local_address = self.proto.reader.transport.get_extra_info('sockname') + if len(self._local_address) == 4: + self._local_address = self._local_address[:2] + return self._local_address + + @property + def peer_address(self): + """ + Peer endpoint address as a tuple + """ + if not self._peer_address: + self._peer_address = self.proto.reader.transport.get_extra_info('peername') + if len(self._peer_address) == 4: + self._peer_address = self._peer_address[:2] + return self._peer_address + + def once(self): + """ + The base class directly is used in conjunction with + the :class:`ws4py.manager.WebSocketManager` which is + not actually used with the asyncio implementation + of ws4py. So let's make it clear it shan't be used. + """ + raise NotImplemented() + + def close_connection(self): + """ + Close the underlying transport + """ + @asyncio.coroutine + def closeit(): + yield from self.proto.writer.drain() + self.proto.writer.close() + asyncio.async(closeit()) + + def _write(self, data): + """ + Write to the underlying transport + """ + @asyncio.coroutine + def sendit(data): + self.proto.writer.write(data) + yield from self.proto.writer.drain() + asyncio.async(sendit(data)) + + @asyncio.coroutine + def run(self): + """ + Coroutine that runs until the websocket + exchange is terminated. It also calls the + `opened()` method to indicate the exchange + has started. + """ + self.started = True + try: + self.opened() + reader = self.proto.reader + while True: + data = yield from reader.read(self.reading_buffer_size) + if not self.process(data): + return False + finally: + self.terminate() + + return True + +class EchoWebSocket(WebSocket): + def received_message(self, message): + """ + Automatically sends back the provided ``message`` to + its originating endpoint. + """ + self.send(message.data, message.is_binary) diff --git a/game/python-extra/ws4py/client/__init__.py b/game/python-extra/ws4py/client/__init__.py new file mode 100644 index 0000000..411638f --- /dev/null +++ b/game/python-extra/ws4py/client/__init__.py @@ -0,0 +1,344 @@ +# -*- coding: utf-8 -*- +from base64 import b64encode +from hashlib import sha1 +import os +import socket +import ssl + +from ws4py import WS_KEY, WS_VERSION +from ws4py.exc import HandshakeError +from ws4py.websocket import WebSocket +from ws4py.compat import urlsplit + +__all__ = ['WebSocketBaseClient'] + +class WebSocketBaseClient(WebSocket): + def __init__(self, url, protocols=None, extensions=None, + heartbeat_freq=None, ssl_options=None, headers=None, exclude_headers=None): + """ + A websocket client that implements :rfc:`6455` and provides a simple + interface to communicate with a websocket server. + + This class works on its own but will block if not run in + its own thread. + + When an instance of this class is created, a :py:mod:`socket` + is created. If the connection is a TCP socket, + the nagle's algorithm is disabled. + + The address of the server will be extracted from the given + websocket url. + + The websocket key is randomly generated, reset the + `key` attribute if you want to provide yours. + + For instance to create a TCP client: + + .. code-block:: python + + >>> from ws4py.client import WebSocketBaseClient + >>> ws = WebSocketBaseClient('ws://localhost/ws') + + + Here is an example for a TCP client over SSL: + + .. code-block:: python + + >>> from ws4py.client import WebSocketBaseClient + >>> ws = WebSocketBaseClient('wss://localhost/ws') + + + Finally an example of a Unix-domain connection: + + .. code-block:: python + + >>> from ws4py.client import WebSocketBaseClient + >>> ws = WebSocketBaseClient('ws+unix:///tmp/my.sock') + + Note that in this case, the initial Upgrade request + will be sent to ``/``. You may need to change this + by setting the resource explicitely before connecting: + + .. code-block:: python + + >>> from ws4py.client import WebSocketBaseClient + >>> ws = WebSocketBaseClient('ws+unix:///tmp/my.sock') + >>> ws.resource = '/ws' + >>> ws.connect() + + You may provide extra headers by passing a list of tuples + which must be unicode objects. + + """ + self.url = url + self.host = None + self.scheme = None + self.port = None + self.unix_socket_path = None + self.resource = None + self.ssl_options = ssl_options or {} + self.extra_headers = headers or [] + self.exclude_headers = exclude_headers or [] + self.exclude_headers = [x.lower() for x in self.exclude_headers] + + if self.scheme == "wss": + # Prevent check_hostname requires server_hostname (ref #187) + if "cert_reqs" not in self.ssl_options: + self.ssl_options["cert_reqs"] = ssl.CERT_NONE + + self._parse_url() + + if self.unix_socket_path: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0) + else: + # Let's handle IPv4 and IPv6 addresses + # Simplified from CherryPy's code + try: + family, socktype, proto, canonname, sa = socket.getaddrinfo(self.host, self.port, + socket.AF_UNSPEC, + socket.SOCK_STREAM, + 0, socket.AI_PASSIVE)[0] + except socket.gaierror: + family = socket.AF_INET + if self.host.startswith('::'): + family = socket.AF_INET6 + + socktype = socket.SOCK_STREAM + proto = 0 + canonname = "" + sa = (self.host, self.port, 0, 0) + + sock = socket.socket(family, socktype, proto) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if hasattr(socket, 'AF_INET6') and family == socket.AF_INET6 and \ + self.host.startswith('::'): + try: + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + except (AttributeError, socket.error): + pass + + WebSocket.__init__(self, sock, protocols=protocols, + extensions=extensions, + heartbeat_freq=heartbeat_freq) + + self.stream.always_mask = True + self.stream.expect_masking = False + self.key = b64encode(os.urandom(16)) + + # Adpated from: https://github.com/liris/websocket-client/blob/master/websocket.py#L105 + def _parse_url(self): + """ + Parses a URL which must have one of the following forms: + + - ws://host[:port][path] + - wss://host[:port][path] + - ws+unix:///path/to/my.socket + + In the first two cases, the ``host`` and ``port`` + attributes will be set to the parsed values. If no port + is explicitely provided, it will be either 80 or 443 + based on the scheme. Also, the ``resource`` attribute is + set to the path segment of the URL (alongside any querystring). + + In addition, if the scheme is ``ws+unix``, the + ``unix_socket_path`` attribute is set to the path to + the Unix socket while the ``resource`` attribute is + set to ``/``. + """ + # Python 2.6.1 and below don't parse ws or wss urls properly. netloc is empty. + # See: https://github.com/Lawouach/WebSocket-for-Python/issues/59 + scheme, url = self.url.split(":", 1) + + parsed = urlsplit(url, scheme="http") + if parsed.hostname: + self.host = parsed.hostname + elif '+unix' in scheme: + self.host = 'localhost' + else: + raise ValueError("Invalid hostname from: %s", self.url) + + if parsed.port: + self.port = parsed.port + + if scheme == "ws": + if not self.port: + self.port = 80 + elif scheme == "wss": + if not self.port: + self.port = 443 + elif scheme in ('ws+unix', 'wss+unix'): + pass + else: + raise ValueError("Invalid scheme: %s" % scheme) + + if parsed.path: + resource = parsed.path + else: + resource = "/" + + if '+unix' in scheme: + self.unix_socket_path = resource + resource = '/' + + if parsed.query: + resource += "?" + parsed.query + + self.scheme = scheme + self.resource = resource + + @property + def bind_addr(self): + """ + Returns the Unix socket path if or a tuple + ``(host, port)`` depending on the initial + URL's scheme. + """ + return self.unix_socket_path or (self.host, self.port) + + def close(self, code=1000, reason=''): + """ + Initiate the closing handshake with the server. + """ + if not self.client_terminated: + self.client_terminated = True + self._write(self.stream.close(code=code, reason=reason).single(mask=True)) + + def connect(self): + """ + Connects this websocket and starts the upgrade handshake + with the remote endpoint. + """ + if self.scheme == "wss": + # default port is now 443; upgrade self.sender to send ssl + self.sock = ssl.wrap_socket(self.sock, **self.ssl_options) + self._is_secure = True + + self.sock.connect(self.bind_addr) + + self._write(self.handshake_request) + + response = b'' + doubleCLRF = b'\r\n\r\n' + while True: + bytes = self.sock.recv(128) + if not bytes: + break + response += bytes + if doubleCLRF in response: + break + + if not response: + self.close_connection() + raise HandshakeError("Invalid response") + + headers, _, body = response.partition(doubleCLRF) + response_line, _, headers = headers.partition(b'\r\n') + + try: + self.process_response_line(response_line) + self.protocols, self.extensions = self.process_handshake_header(headers) + except HandshakeError: + self.close_connection() + raise + + self.handshake_ok() + if body: + self.process(body) + + @property + def handshake_headers(self): + """ + List of headers appropriate for the upgrade + handshake. + """ + headers = [ + ('Host', '%s:%s' % (self.host, self.port)), + ('Connection', 'Upgrade'), + ('Upgrade', 'websocket'), + ('Sec-WebSocket-Key', self.key.decode('utf-8')), + ('Sec-WebSocket-Version', str(max(WS_VERSION))) + ] + + if self.protocols: + headers.append(('Sec-WebSocket-Protocol', ','.join(self.protocols))) + + if self.extra_headers: + headers.extend(self.extra_headers) + + if not any(x for x in headers if x[0].lower() == 'origin') and \ + 'origin' not in self.exclude_headers: + + scheme, url = self.url.split(":", 1) + parsed = urlsplit(url, scheme="http") + if parsed.hostname: + self.host = parsed.hostname + else: + self.host = 'localhost' + origin = scheme + '://' + self.host + if parsed.port: + origin = origin + ':' + str(parsed.port) + headers.append(('Origin', origin)) + + headers = [x for x in headers if x[0].lower() not in self.exclude_headers] + + return headers + + @property + def handshake_request(self): + """ + Prepare the request to be sent for the upgrade handshake. + """ + headers = self.handshake_headers + request = [("GET %s HTTP/1.1" % self.resource).encode('utf-8')] + for header, value in headers: + request.append(("%s: %s" % (header, value)).encode('utf-8')) + request.append(b'\r\n') + + return b'\r\n'.join(request) + + def process_response_line(self, response_line): + """ + Ensure that we received a HTTP `101` status code in + response to our request and if not raises :exc:`HandshakeError`. + """ + protocol, code, status = response_line.split(b' ', 2) + if code != b'101': + raise HandshakeError("Invalid response status: %s %s" % (code, status)) + + def process_handshake_header(self, headers): + """ + Read the upgrade handshake's response headers and + validate them against :rfc:`6455`. + """ + protocols = [] + extensions = [] + + headers = headers.strip() + + for header_line in headers.split(b'\r\n'): + header, value = header_line.split(b':', 1) + header = header.strip().lower() + value = value.strip().lower() + + if header == b'upgrade' and value != b'websocket': + raise HandshakeError("Invalid Upgrade header: %s" % value) + + elif header == b'connection' and value != b'upgrade': + raise HandshakeError("Invalid Connection header: %s" % value) + + elif header == b'sec-websocket-accept': + match = b64encode(sha1(self.key + WS_KEY).digest()) + if value != match.lower(): + raise HandshakeError("Invalid challenge response: %s" % value) + + elif header == b'sec-websocket-protocol': + protocols.extend([x.strip() for x in value.split(b',')]) + + elif header == b'sec-websocket-extensions': + extensions.extend([x.strip() for x in value.split(b',')]) + + return protocols, extensions + + def handshake_ok(self): + self.opened() diff --git a/game/python-extra/ws4py/client/geventclient.py b/game/python-extra/ws4py/client/geventclient.py new file mode 100644 index 0000000..b64a17e --- /dev/null +++ b/game/python-extra/ws4py/client/geventclient.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +import copy + +import gevent +from gevent import Greenlet +from gevent.queue import Queue + +from ws4py.client import WebSocketBaseClient + +__all__ = ['WebSocketClient'] + +class WebSocketClient(WebSocketBaseClient): + def __init__(self, url, protocols=None, extensions=None, heartbeat_freq=None, ssl_options=None, headers=None, exclude_headers=None): + """ + WebSocket client that executes the + :meth:`run() <ws4py.websocket.WebSocket.run>` into a gevent greenlet. + + .. code-block:: python + + ws = WebSocketClient('ws://localhost:9000/echo', protocols=['http-only', 'chat']) + ws.connect() + + ws.send("Hello world") + + def incoming(): + while True: + m = ws.receive() + if m is not None: + print str(m) + else: + break + + def outgoing(): + for i in range(0, 40, 5): + ws.send("*" * i) + + greenlets = [ + gevent.spawn(incoming), + gevent.spawn(outgoing), + ] + gevent.joinall(greenlets) + """ + WebSocketBaseClient.__init__(self, url, protocols, extensions, heartbeat_freq, + ssl_options=ssl_options, headers=headers, exclude_headers=exclude_headers) + self._th = Greenlet(self.run) + + self.messages = Queue() + """ + Queue that will hold received messages. + """ + + def handshake_ok(self): + """ + Called when the upgrade handshake has completed + successfully. + + Starts the client's thread. + """ + self._th.start() + + def received_message(self, message): + """ + Override the base class to store the incoming message + in the `messages` queue. + """ + self.messages.put(copy.deepcopy(message)) + + def closed(self, code, reason=None): + """ + Puts a :exc:`StopIteration` as a message into the + `messages` queue. + """ + # When the connection is closed, put a StopIteration + # on the message queue to signal there's nothing left + # to wait for + self.messages.put(StopIteration) + + def receive(self, block=True): + """ + Returns messages that were stored into the + `messages` queue and returns `None` when the + websocket is terminated or closed. + `block` is passed though the gevent queue `.get()` method, which if + True will block until an item in the queue is available. Set this to + False if you just want to check the queue, which will raise an + Empty exception you need to handle if there is no message to return. + """ + # If the websocket was terminated and there are no messages + # left in the queue, return None immediately otherwise the client + # will block forever + if self.terminated and self.messages.empty(): + return None + message = self.messages.get(block=block) + if message is StopIteration: + return None + return message diff --git a/game/python-extra/ws4py/client/threadedclient.py b/game/python-extra/ws4py/client/threadedclient.py new file mode 100644 index 0000000..b033f32 --- /dev/null +++ b/game/python-extra/ws4py/client/threadedclient.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +import threading + +from ws4py.client import WebSocketBaseClient + +__all__ = ['WebSocketClient'] + +class WebSocketClient(WebSocketBaseClient): + def __init__(self, url, protocols=None, extensions=None, heartbeat_freq=None, + ssl_options=None, headers=None, exclude_headers=None): + """ + .. code-block:: python + + from ws4py.client.threadedclient import WebSocketClient + + class EchoClient(WebSocketClient): + def opened(self): + for i in range(0, 200, 25): + self.send("*" * i) + + def closed(self, code, reason): + print(("Closed down", code, reason)) + + def received_message(self, m): + print("=> %d %s" % (len(m), str(m))) + + try: + ws = EchoClient('ws://localhost:9000/echo', protocols=['http-only', 'chat']) + ws.connect() + except KeyboardInterrupt: + ws.close() + + """ + WebSocketBaseClient.__init__(self, url, protocols, extensions, heartbeat_freq, + ssl_options, headers=headers, exclude_headers=exclude_headers) + self._th = threading.Thread(target=self.run, name='WebSocketClient') + self._th.daemon = True + + @property + def daemon(self): + """ + `True` if the client's thread is set to be a daemon thread. + """ + return self._th.daemon + + @daemon.setter + def daemon(self, flag): + """ + Set to `True` if the client's thread should be a daemon. + """ + self._th.daemon = flag + + def run_forever(self): + """ + Simply blocks the thread until the + websocket has terminated. + """ + while not self.terminated: + self._th.join(timeout=0.1) + + def handshake_ok(self): + """ + Called when the upgrade handshake has completed + successfully. + + Starts the client's thread. + """ + self._th.start() + +if __name__ == '__main__': + from ws4py.client.threadedclient import WebSocketClient + + class EchoClient(WebSocketClient): + def opened(self): + def data_provider(): + for i in range(0, 200, 25): + yield "#" * i + + self.send(data_provider()) + + for i in range(0, 200, 25): + self.send("*" * i) + + def closed(self, code, reason): + print(("Closed down", code, reason)) + + def received_message(self, m): + print("#%d" % len(m)) + if len(m) == 175: + self.close(reason='bye bye') + + try: + ws = EchoClient('ws://localhost:9000/ws', protocols=['http-only', 'chat'], + headers=[('X-Test', 'hello there')]) + ws.connect() + ws.run_forever() + except KeyboardInterrupt: + ws.close() diff --git a/game/python-extra/ws4py/client/tornadoclient.py b/game/python-extra/ws4py/client/tornadoclient.py new file mode 100644 index 0000000..22478e6 --- /dev/null +++ b/game/python-extra/ws4py/client/tornadoclient.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +import ssl + +from tornado import iostream, escape +from ws4py.client import WebSocketBaseClient +from ws4py.exc import HandshakeError + +__all__ = ['TornadoWebSocketClient'] + +class TornadoWebSocketClient(WebSocketBaseClient): + def __init__(self, url, protocols=None, extensions=None, + io_loop=None, ssl_options=None, headers=None, exclude_headers=None): + """ + .. code-block:: python + + from tornado import ioloop + + class MyClient(TornadoWebSocketClient): + def opened(self): + for i in range(0, 200, 25): + self.send("*" * i) + + def received_message(self, m): + print((m, len(str(m)))) + + def closed(self, code, reason=None): + ioloop.IOLoop.instance().stop() + + ws = MyClient('ws://localhost:9000/echo', protocols=['http-only', 'chat']) + ws.connect() + + ioloop.IOLoop.instance().start() + """ + WebSocketBaseClient.__init__(self, url, protocols, extensions, + ssl_options=ssl_options, headers=headers, exclude_headers=exclude_headers) + if self.scheme == "wss": + self.sock = ssl.wrap_socket(self.sock, do_handshake_on_connect=False, **self.ssl_options) + self._is_secure = True + self.io = iostream.SSLIOStream(self.sock, io_loop, ssl_options=self.ssl_options) + else: + self.io = iostream.IOStream(self.sock, io_loop) + self.io_loop = io_loop + + def connect(self): + """ + Connects the websocket and initiate the upgrade handshake. + """ + self.io.set_close_callback(self.__connection_refused) + self.io.connect((self.host, int(self.port)), self.__send_handshake) + + def _write(self, b): + """ + Trying to prevent a write operation + on an already closed websocket stream. + + This cannot be bullet proof but hopefully + will catch almost all use cases. + """ + if self.terminated: + raise RuntimeError("Cannot send on a terminated websocket") + + self.io.write(b) + + def __connection_refused(self, *args, **kwargs): + self.server_terminated = True + self.closed(1005, 'Connection refused') + + def __send_handshake(self): + self.io.set_close_callback(self.__connection_closed) + self.io.write(escape.utf8(self.handshake_request), + self.__handshake_sent) + + def __connection_closed(self, *args, **kwargs): + self.server_terminated = True + self.closed(1006, 'Connection closed during handshake') + + def __handshake_sent(self): + self.io.read_until(b"\r\n\r\n", self.__handshake_completed) + + def __handshake_completed(self, data): + self.io.set_close_callback(None) + try: + response_line, _, headers = data.partition(b'\r\n') + self.process_response_line(response_line) + protocols, extensions = self.process_handshake_header(headers) + except HandshakeError: + self.close_connection() + raise + + self.opened() + self.io.set_close_callback(self.__stream_closed) + self.io.read_bytes(self.reading_buffer_size, self.__fetch_more) + + def __fetch_more(self, bytes): + try: + should_continue = self.process(bytes) + except: + should_continue = False + + if should_continue: + self.io.read_bytes(self.reading_buffer_size, self.__fetch_more) + else: + self.__gracefully_terminate() + + def __gracefully_terminate(self): + self.client_terminated = self.server_terminated = True + + try: + if not self.stream.closing: + self.closed(1006) + finally: + self.close_connection() + + def __stream_closed(self, *args, **kwargs): + self.io.set_close_callback(None) + code = 1006 + reason = None + if self.stream.closing: + code, reason = self.stream.closing.code, self.stream.closing.reason + self.closed(code, reason) + self.stream._cleanup() + + def close_connection(self): + """ + Close the underlying connection + """ + self.io.close() + +if __name__ == '__main__': + from tornado import ioloop + + class MyClient(TornadoWebSocketClient): + def opened(self): + def data_provider(): + for i in range(0, 200, 25): + yield "#" * i + + self.send(data_provider()) + + for i in range(0, 200, 25): + self.send("*" * i) + + def received_message(self, m): + print("#%d" % len(m)) + if len(m) == 175: + self.close() + + def closed(self, code, reason=None): + ioloop.IOLoop.instance().stop() + print(("Closed down", code, reason)) + + ws = MyClient('ws://localhost:9000/ws', protocols=['http-only', 'chat']) + ws.connect() + + ioloop.IOLoop.instance().start() diff --git a/game/python-extra/ws4py/compat.py b/game/python-extra/ws4py/compat.py new file mode 100644 index 0000000..e986e33 --- /dev/null +++ b/game/python-extra/ws4py/compat.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +__doc__ = """ +This compatibility module is inspired by the one found +in CherryPy. It provides a common entry point for the various +functions and types that are used with ws4py but which +differ from Python 2.x to Python 3.x + +There are likely better ways for some of them so feel +free to provide patches. + +Note this has been tested against 2.7 and 3.3 only but +should hopefully work fine with other versions too. +""" +import sys + +if sys.version_info >= (3, 0): + py3k = True + from urllib.parse import urlsplit + range = range + unicode = str + basestring = (bytes, str) + _ord = ord + + def get_connection(fileobj): + return fileobj.raw._sock + + def detach_connection(fileobj): + fileobj.detach() + + def ord(c): + if isinstance(c, int): + return c + return _ord(c) +else: + py3k = False + from urlparse import urlsplit + range = xrange + unicode = unicode + basestring = basestring + ord = ord + + def get_connection(fileobj): + return fileobj._sock + + def detach_connection(fileobj): + fileobj._sock = None diff --git a/game/python-extra/ws4py/exc.py b/game/python-extra/ws4py/exc.py new file mode 100644 index 0000000..bfefea4 --- /dev/null +++ b/game/python-extra/ws4py/exc.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +__all__ = ['WebSocketException', 'FrameTooLargeException', 'ProtocolException', + 'UnsupportedFrameTypeException', 'TextFrameEncodingException', + 'UnsupportedFrameTypeException', 'TextFrameEncodingException', + 'StreamClosed', 'HandshakeError', 'InvalidBytesError'] + +class WebSocketException(Exception): pass + +class ProtocolException(WebSocketException): pass + +class FrameTooLargeException(WebSocketException): pass + +class UnsupportedFrameTypeException(WebSocketException): pass + +class TextFrameEncodingException(WebSocketException): pass + +class InvalidBytesError(WebSocketException): pass + +class StreamClosed(Exception): pass + +class HandshakeError(WebSocketException): + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return self.msg diff --git a/game/python-extra/ws4py/framing.py b/game/python-extra/ws4py/framing.py new file mode 100644 index 0000000..5046167 --- /dev/null +++ b/game/python-extra/ws4py/framing.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- +from struct import pack, unpack + +from ws4py.exc import FrameTooLargeException, ProtocolException +from ws4py.compat import py3k, ord, range + +# Frame opcodes defined in the spec. +OPCODE_CONTINUATION = 0x0 +OPCODE_TEXT = 0x1 +OPCODE_BINARY = 0x2 +OPCODE_CLOSE = 0x8 +OPCODE_PING = 0x9 +OPCODE_PONG = 0xa + +__all__ = ['Frame'] + +class Frame(object): + def __init__(self, opcode=None, body=b'', masking_key=None, fin=0, rsv1=0, rsv2=0, rsv3=0): + """ + Implements the framing protocol as defined by RFC 6455. + + .. code-block:: python + :linenos: + + >>> test_mask = 'XXXXXX' # perhaps from os.urandom(4) + >>> f = Frame(OPCODE_TEXT, 'hello world', masking_key=test_mask, fin=1) + >>> bytes = f.build() + >>> bytes.encode('hex') + '818bbe04e66ad6618a06d1249105cc6882' + >>> f = Frame() + >>> f.parser.send(bytes[0]) + 1 + >>> f.parser.send(bytes[1]) + 4 + + .. seealso:: Data Framing http://tools.ietf.org/html/rfc6455#section-5.2 + """ + if not isinstance(body, bytes): + raise TypeError("The body must be properly encoded") + + self.opcode = opcode + self.body = body + self.masking_key = masking_key + self.fin = fin + self.rsv1 = rsv1 + self.rsv2 = rsv2 + self.rsv3 = rsv3 + self.payload_length = len(body) + + self._parser = None + + @property + def parser(self): + if self._parser is None: + self._parser = self._parsing() + # Python generators must be initialized once. + next(self.parser) + return self._parser + + def _cleanup(self): + if self._parser: + self._parser.close() + self._parser = None + + def build(self): + """ + Builds a frame from the instance's attributes and returns + its bytes representation. + """ + header = b'' + + if self.fin > 0x1: + raise ValueError('FIN bit parameter must be 0 or 1') + + if 0x3 <= self.opcode <= 0x7 or 0xB <= self.opcode: + raise ValueError('Opcode cannot be a reserved opcode') + + ## +-+-+-+-+-------+ + ## |F|R|R|R| opcode| + ## |I|S|S|S| (4) | + ## |N|V|V|V| | + ## | |1|2|3| | + ## +-+-+-+-+-------+ + header = pack('!B', ((self.fin << 7) + | (self.rsv1 << 6) + | (self.rsv2 << 5) + | (self.rsv3 << 4) + | self.opcode)) + + ## +-+-------------+-------------------------------+ + ## |M| Payload len | Extended payload length | + ## |A| (7) | (16/63) | + ## |S| | (if payload len==126/127) | + ## |K| | | + ## +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + ## | Extended payload length continued, if payload len == 127 | + ## + - - - - - - - - - - - - - - - +-------------------------------+ + if self.masking_key: mask_bit = 1 << 7 + else: mask_bit = 0 + + length = self.payload_length + if length < 126: + header += pack('!B', (mask_bit | length)) + elif length < (1 << 16): + header += pack('!B', (mask_bit | 126)) + pack('!H', length) + elif length < (1 << 63): + header += pack('!B', (mask_bit | 127)) + pack('!Q', length) + else: + raise FrameTooLargeException() + + ## + - - - - - - - - - - - - - - - +-------------------------------+ + ## | |Masking-key, if MASK set to 1 | + ## +-------------------------------+-------------------------------+ + ## | Masking-key (continued) | Payload Data | + ## +-------------------------------- - - - - - - - - - - - - - - - + + ## : Payload Data continued ... : + ## + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + ## | Payload Data continued ... | + ## +---------------------------------------------------------------+ + body = self.body + if not self.masking_key: + return bytes(header + body) + + return bytes(header + self.masking_key + self.mask(body)) + + def _parsing(self): + """ + Generator to parse bytes into a frame. Yields until + enough bytes have been read or an error is met. + """ + buf = b'' + some_bytes = b'' + + # yield until we get the first header's byte + while not some_bytes: + some_bytes = (yield 1) + + first_byte = some_bytes[0] if isinstance(some_bytes, bytearray) else ord(some_bytes[0]) + # frame-fin = %x0 ; more frames of this message follow + # / %x1 ; final frame of this message + self.fin = (first_byte >> 7) & 1 + self.rsv1 = (first_byte >> 6) & 1 + self.rsv2 = (first_byte >> 5) & 1 + self.rsv3 = (first_byte >> 4) & 1 + self.opcode = first_byte & 0xf + + # frame-rsv1 = %x0 ; 1 bit, MUST be 0 unless negotiated otherwise + # frame-rsv2 = %x0 ; 1 bit, MUST be 0 unless negotiated otherwise + # frame-rsv3 = %x0 ; 1 bit, MUST be 0 unless negotiated otherwise + if self.rsv1 or self.rsv2 or self.rsv3: + raise ProtocolException() + + # control frames between 3 and 7 as well as above 0xA are currently reserved + if 2 < self.opcode < 8 or self.opcode > 0xA: + raise ProtocolException() + + # control frames cannot be fragmented + if self.opcode > 0x7 and self.fin == 0: + raise ProtocolException() + + # do we already have enough some_bytes to continue? + some_bytes = some_bytes[1:] if some_bytes and len(some_bytes) > 1 else b'' + + # Yield until we get the second header's byte + while not some_bytes: + some_bytes = (yield 1) + + second_byte = some_bytes[0] if isinstance(some_bytes, bytearray) else ord(some_bytes[0]) + mask = (second_byte >> 7) & 1 + self.payload_length = second_byte & 0x7f + + # All control frames MUST have a payload length of 125 some_bytes or less + if self.opcode > 0x7 and self.payload_length > 125: + raise FrameTooLargeException() + + if some_bytes and len(some_bytes) > 1: + buf = some_bytes[1:] + some_bytes = buf + else: + buf = b'' + some_bytes = b'' + + if self.payload_length == 127: + # This will compute the actual application data size + if len(buf) < 8: + nxt_buf_size = 8 - len(buf) + some_bytes = (yield nxt_buf_size) + some_bytes = buf + (some_bytes or b'') + while len(some_bytes) < 8: + b = (yield 8 - len(some_bytes)) + if b is not None: + some_bytes = some_bytes + b + if len(some_bytes) > 8: + buf = some_bytes[8:] + some_bytes = some_bytes[:8] + else: + some_bytes = buf[:8] + buf = buf[8:] + extended_payload_length = some_bytes + self.payload_length = unpack( + '!Q', extended_payload_length)[0] + if self.payload_length > 0x7FFFFFFFFFFFFFFF: + raise FrameTooLargeException() + elif self.payload_length == 126: + if len(buf) < 2: + nxt_buf_size = 2 - len(buf) + some_bytes = (yield nxt_buf_size) + some_bytes = buf + (some_bytes or b'') + while len(some_bytes) < 2: + b = (yield 2 - len(some_bytes)) + if b is not None: + some_bytes = some_bytes + b + if len(some_bytes) > 2: + buf = some_bytes[2:] + some_bytes = some_bytes[:2] + else: + some_bytes = buf[:2] + buf = buf[2:] + extended_payload_length = some_bytes + self.payload_length = unpack( + '!H', extended_payload_length)[0] + + if mask: + if len(buf) < 4: + nxt_buf_size = 4 - len(buf) + some_bytes = (yield nxt_buf_size) + some_bytes = buf + (some_bytes or b'') + while not some_bytes or len(some_bytes) < 4: + b = (yield 4 - len(some_bytes)) + if b is not None: + some_bytes = some_bytes + b + if len(some_bytes) > 4: + buf = some_bytes[4:] + else: + some_bytes = buf[:4] + buf = buf[4:] + self.masking_key = some_bytes + + if len(buf) < self.payload_length: + nxt_buf_size = self.payload_length - len(buf) + some_bytes = (yield nxt_buf_size) + some_bytes = buf + (some_bytes or b'') + while len(some_bytes) < self.payload_length: + l = self.payload_length - len(some_bytes) + b = (yield l) + if b is not None: + some_bytes = some_bytes + b + else: + if self.payload_length == len(buf): + some_bytes = buf + else: + some_bytes = buf[:self.payload_length] + + self.body = some_bytes + yield + + def mask(self, data): + """ + Performs the masking or unmasking operation on data + using the simple masking algorithm: + + .. + j = i MOD 4 + transformed-octet-i = original-octet-i XOR masking-key-octet-j + + """ + masked = bytearray(data) + if py3k: key = self.masking_key + else: key = map(ord, self.masking_key) + for i in range(len(data)): + masked[i] = masked[i] ^ key[i%4] + return masked + unmask = mask diff --git a/game/python-extra/ws4py/manager.py b/game/python-extra/ws4py/manager.py new file mode 100644 index 0000000..20c215f --- /dev/null +++ b/game/python-extra/ws4py/manager.py @@ -0,0 +1,368 @@ +# -*- coding: utf-8 -*- +__doc__ = """ +The manager module provides a selected classes to +handle websocket's execution. + +Initially the rationale was to: + +- Externalize the way the CherryPy server had been setup + as its websocket management was too tightly coupled with + the plugin implementation. +- Offer a management that could be used by other + server or client implementations. +- Move away from the threaded model to the event-based + model by relying on `select` or `epoll` (when available). + + +A simple usage for handling websocket clients: + +.. code-block:: python + + from ws4py.client import WebSocketBaseClient + from ws4py.manager import WebSocketManager + + m = WebSocketManager() + + class EchoClient(WebSocketBaseClient): + def handshake_ok(self): + m.add(self) # register the client once the handshake is done + + def received_message(self, msg): + print str(msg) + + m.start() + + client = EchoClient('ws://localhost:9000/ws') + client.connect() + + m.join() # blocks forever + +Managers are not compulsory but hopefully will help your +workflow. For clients, you can still rely on threaded, gevent or +tornado based implementations of course. +""" +import logging +import select +import threading +import time + +from ws4py import format_addresses +from ws4py.compat import py3k + +logger = logging.getLogger('ws4py') + +class SelectPoller(object): + def __init__(self, timeout=0.1): + """ + A socket poller that uses the `select` + implementation to determines which + file descriptors have data available to read. + + It is available on all platforms. + """ + self._fds = [] + self.timeout = timeout + + def release(self): + """ + Cleanup resources. + """ + self._fds = [] + + def register(self, fd): + """ + Register a new file descriptor to be + part of the select polling next time around. + """ + if fd not in self._fds: + self._fds.append(fd) + + def unregister(self, fd): + """ + Unregister the given file descriptor. + """ + if fd in self._fds: + self._fds.remove(fd) + + def poll(self): + """ + Polls once and returns a list of + ready-to-be-read file descriptors. + """ + if not self._fds: + time.sleep(self.timeout) + return [] + try: + r, w, x = select.select(self._fds, [], [], self.timeout) + except IOError as e: + return [] + return r + +class EPollPoller(object): + def __init__(self, timeout=0.1): + """ + An epoll poller that uses the ``epoll`` + implementation to determines which + file descriptors have data available to read. + + Available on Unix flavors mostly. + """ + self.poller = select.epoll() + self.timeout = timeout + + def release(self): + """ + Cleanup resources. + """ + self.poller.close() + + def register(self, fd): + """ + Register a new file descriptor to be + part of the select polling next time around. + """ + try: + self.poller.register(fd, select.EPOLLIN | select.EPOLLPRI) + except IOError: + pass + + def unregister(self, fd): + """ + Unregister the given file descriptor. + """ + self.poller.unregister(fd) + + def poll(self): + """ + Polls once and yields each ready-to-be-read + file-descriptor + """ + try: + events = self.poller.poll(timeout=self.timeout) + except IOError: + events = [] + + for fd, event in events: + if event | select.EPOLLIN | select.EPOLLPRI: + yield fd + +class KQueuePoller(object): + def __init__(self, timeout=0.1): + """ + An epoll poller that uses the ``epoll`` + implementation to determines which + file descriptors have data available to read. + + Available on Unix flavors mostly. + """ + self.poller = select.epoll() + self.timeout = timeout + + def release(self): + """ + Cleanup resources. + """ + self.poller.close() + + def register(self, fd): + """ + Register a new file descriptor to be + part of the select polling next time around. + """ + try: + self.poller.register(fd, select.EPOLLIN | select.EPOLLPRI) + except IOError: + pass + + def unregister(self, fd): + """ + Unregister the given file descriptor. + """ + self.poller.unregister(fd) + + def poll(self): + """ + Polls once and yields each ready-to-be-read + file-descriptor + """ + try: + events = self.poller.poll(timeout=self.timeout) + except IOError: + events = [] + for fd, event in events: + if event | select.EPOLLIN | select.EPOLLPRI: + yield fd + +class WebSocketManager(threading.Thread): + def __init__(self, poller=None): + """ + An event-based websocket manager. By event-based, we mean + that the websockets will be called when their + sockets have data to be read from. + + The manager itself runs in its own thread as not to + be the blocking mainloop of your application. + + The poller's implementation is automatically chosen + with ``epoll`` if available else ``select`` unless you + provide your own ``poller``. + """ + threading.Thread.__init__(self) + self.name = "WebSocketManager" + self.lock = threading.Lock() + self.websockets = {} + self.running = False + + if poller: + self.poller = poller + else: + if hasattr(select, "epoll"): + self.poller = EPollPoller() + logger.info("Using epoll") + else: + self.poller = SelectPoller() + logger.info("Using select as epoll is not available") + + def __len__(self): + return len(self.websockets) + + def __iter__(self): + if py3k: + return iter(self.websockets.values()) + else: + return self.websockets.itervalues() + + def __contains__(self, ws): + fd = ws.sock.fileno() + # just in case the file descriptor was reused + # we actually check the instance (well, this might + # also have been reused...) + return self.websockets.get(fd) is ws + + def add(self, websocket): + """ + Manage a new websocket. + + First calls its :meth:`opened() <ws4py.websocket.WebSocket.opened>` + method and register its socket against the poller + for reading events. + """ + if websocket in self: + return + + logger.info("Managing websocket %s" % format_addresses(websocket)) + websocket.opened() + with self.lock: + fd = websocket.sock.fileno() + self.websockets[fd] = websocket + self.poller.register(fd) + + def remove(self, websocket): + """ + Remove the given ``websocket`` from the manager. + + This does not call its :meth:`closed() <ws4py.websocket.WebSocket.closed>` + method as it's out-of-band by your application + or from within the manager's run loop. + """ + if websocket not in self: + return + + logger.info("Removing websocket %s" % format_addresses(websocket)) + with self.lock: + fd = websocket.sock.fileno() + self.websockets.pop(fd, None) + self.poller.unregister(fd) + + def stop(self): + """ + Mark the manager as terminated and + releases its resources. + """ + self.running = False + with self.lock: + self.websockets.clear() + self.poller.release() + + def run(self): + """ + Manager's mainloop executed from within a thread. + + Constantly poll for read events and, when available, + call related websockets' `once` method to + read and process the incoming data. + + If the :meth:`once() <ws4py.websocket.WebSocket.once>` + method returns a `False` value, its :meth:`terminate() <ws4py.websocket.WebSocket.terminate>` + method is also applied to properly close + the websocket and its socket is unregistered from the poller. + + Note that websocket shouldn't take long to process + their data or they will block the remaining + websockets with data to be handled. As for what long means, + it's up to your requirements. + """ + self.running = True + while self.running: + with self.lock: + polled = self.poller.poll() + if not self.running: + break + + for fd in polled: + if not self.running: + break + + ws = self.websockets.get(fd) + if ws and not ws.terminated: + # I don't know what kind of errors might spew out of here + # but they probably shouldn't crash the entire server. + try: + x = ws.once() + # Treat the error as if once() had returned None + except Exception as e: + x = None + logger.error("Terminating websocket %s due to exception: %s in once method" % (format_addresses(ws), repr(e)) ) + if not x: + with self.lock: + self.websockets.pop(fd, None) + self.poller.unregister(fd) + + if not ws.terminated: + logger.info("Terminating websocket %s" % format_addresses(ws)) + ws.terminate() + + + def close_all(self, code=1001, message='Server is shutting down'): + """ + Execute the :meth:`close() <ws4py.websocket.WebSocket.close>` + method of each registered websockets to initiate the closing handshake. + It doesn't wait for the handshake to complete properly. + """ + with self.lock: + logger.info("Closing all websockets with [%d] '%s'" % (code, message)) + for ws in iter(self): + ws.close(code=code, reason=message) + + def broadcast(self, message, binary=False): + """ + Broadcasts the given message to all registered + websockets, at the time of the call. + + Broadcast may fail on a given registered peer + but this is silent as it's not the method's + purpose to handle websocket's failures. + """ + with self.lock: + websockets = self.websockets.copy() + if py3k: + ws_iter = iter(websockets.values()) + else: + ws_iter = websockets.itervalues() + + for ws in ws_iter: + if not ws.terminated: + try: + ws.send(message, binary) + except: + pass diff --git a/game/python-extra/ws4py/messaging.py b/game/python-extra/ws4py/messaging.py new file mode 100644 index 0000000..f9b0e77 --- /dev/null +++ b/game/python-extra/ws4py/messaging.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +import os +import struct + +from ws4py.framing import Frame, OPCODE_CONTINUATION, OPCODE_TEXT, \ + OPCODE_BINARY, OPCODE_CLOSE, OPCODE_PING, OPCODE_PONG +from ws4py.compat import unicode, py3k + +__all__ = ['Message', 'TextMessage', 'BinaryMessage', 'CloseControlMessage', + 'PingControlMessage', 'PongControlMessage'] + +class Message(object): + def __init__(self, opcode, data=b'', encoding='utf-8'): + """ + A message is a application level entity. It's usually built + from one or many frames. The protocol defines several kind + of messages which are grouped into two sets: + + * data messages which can be text or binary typed + * control messages which provide a mechanism to perform + in-band control communication between peers + + The ``opcode`` indicates the message type and ``data`` is + the possible message payload. + + The payload is held internally as a a :func:`bytearray` as they are + faster than pure strings for append operations. + + Unicode data will be encoded using the provided ``encoding``. + """ + self.opcode = opcode + self._completed = False + self.encoding = encoding + + if isinstance(data, unicode): + if not encoding: + raise TypeError("unicode data without an encoding") + data = data.encode(encoding) + elif isinstance(data, bytearray): + data = bytes(data) + elif not isinstance(data, bytes): + raise TypeError("%s is not a supported data type" % type(data)) + + self.data = data + + def single(self, mask=False): + """ + Returns a frame bytes with the fin bit set and a random mask. + + If ``mask`` is set, automatically mask the frame + using a generated 4-byte token. + """ + mask = os.urandom(4) if mask else None + return Frame(body=self.data, opcode=self.opcode, + masking_key=mask, fin=1).build() + + def fragment(self, first=False, last=False, mask=False): + """ + Returns a :class:`ws4py.framing.Frame` bytes. + + The behavior depends on the given flags: + + * ``first``: the frame uses ``self.opcode`` else a continuation opcode + * ``last``: the frame has its ``fin`` bit set + * ``mask``: the frame is masked using a automatically generated 4-byte token + """ + fin = 1 if last is True else 0 + opcode = self.opcode if first is True else OPCODE_CONTINUATION + mask = os.urandom(4) if mask else None + return Frame(body=self.data, + opcode=opcode, masking_key=mask, + fin=fin).build() + + @property + def completed(self): + """ + Indicates the the message is complete, meaning + the frame's ``fin`` bit was set. + """ + return self._completed + + @completed.setter + def completed(self, state): + """ + Sets the state for this message. Usually + set by the stream's parser. + """ + self._completed = state + + def extend(self, data): + """ + Add more ``data`` to the message. + """ + if isinstance(data, bytes): + self.data += data + elif isinstance(data, bytearray): + self.data += bytes(data) + elif isinstance(data, unicode): + self.data += data.encode(self.encoding) + else: + raise TypeError("%s is not a supported data type" % type(data)) + + def __len__(self): + return len(self.__unicode__()) + + def __str__(self): + if py3k: + return self.data.decode(self.encoding) + return self.data + + def __unicode__(self): + return self.data.decode(self.encoding) + +class TextMessage(Message): + def __init__(self, text=None): + Message.__init__(self, OPCODE_TEXT, text) + + @property + def is_binary(self): + return False + + @property + def is_text(self): + return True + +class BinaryMessage(Message): + def __init__(self, bytes=None): + Message.__init__(self, OPCODE_BINARY, bytes, encoding=None) + + @property + def is_binary(self): + return True + + @property + def is_text(self): + return False + + def __len__(self): + return len(self.data) + +class CloseControlMessage(Message): + def __init__(self, code=1000, reason=''): + data = b"" + if code: + data += struct.pack("!H", code) + if reason is not None: + if isinstance(reason, unicode): + reason = reason.encode('utf-8') + data += reason + + Message.__init__(self, OPCODE_CLOSE, data, 'utf-8') + self.code = code + self.reason = reason + + def __str__(self): + if py3k: + return self.reason.decode('utf-8') + return self.reason + + def __unicode__(self): + return self.reason.decode(self.encoding) + +class PingControlMessage(Message): + def __init__(self, data=None): + Message.__init__(self, OPCODE_PING, data) + +class PongControlMessage(Message): + def __init__(self, data): + Message.__init__(self, OPCODE_PONG, data) diff --git a/game/python-extra/websock/tests/__init__.py b/game/python-extra/ws4py/server/__init__.py index e69de29..e69de29 100644 --- a/game/python-extra/websock/tests/__init__.py +++ b/game/python-extra/ws4py/server/__init__.py diff --git a/game/python-extra/ws4py/server/cherrypyserver.py b/game/python-extra/ws4py/server/cherrypyserver.py new file mode 100644 index 0000000..5b93465 --- /dev/null +++ b/game/python-extra/ws4py/server/cherrypyserver.py @@ -0,0 +1,382 @@ +# -*- coding: utf-8 -*- +__doc__ = """ +WebSocket within CherryPy is a tricky bit since CherryPy is +a threaded server which would choke quickly if each thread +of the server were kept attached to a long living connection +that WebSocket expects. + +In order to work around this constraint, we take some advantage +of some internals of CherryPy as well as the introspection +Python provides. + +Basically, when the WebSocket handshake is complete, we take over +the socket and let CherryPy take back the thread that was +associated with the upgrade request. + +These operations require a bit of work at various levels of +the CherryPy framework but this module takes care of them +and from your application's perspective, this is abstracted. + +Here are the various utilities provided by this module: + + * WebSocketTool: The tool is in charge to perform the + HTTP upgrade and detach the socket from + CherryPy. It runs at various hook points of the + request's processing. Enable that tool at + any path you wish to handle as a WebSocket + handler. + + * WebSocketPlugin: The plugin tracks the instanciated web socket handlers. + It also cleans out websocket handler which connection + have been closed down. The websocket connection then + runs in its own thread that this plugin manages. + +Simple usage example: + +.. code-block:: python + :linenos: + + import cherrypy + from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool + from ws4py.websocket import EchoWebSocket + + cherrypy.config.update({'server.socket_port': 9000}) + WebSocketPlugin(cherrypy.engine).subscribe() + cherrypy.tools.websocket = WebSocketTool() + + class Root(object): + @cherrypy.expose + def index(self): + return 'some HTML with a websocket javascript connection' + + @cherrypy.expose + def ws(self): + pass + + cherrypy.quickstart(Root(), '/', config={'/ws': {'tools.websocket.on': True, + 'tools.websocket.handler_cls': EchoWebSocket}}) + + +Note that you can set the handler class on per-path basis, +meaning you could also dynamically change the class based +on other envrionmental settings (is the user authenticated for ex). +""" +import base64 +from hashlib import sha1 +import inspect +import threading + +import cherrypy +from cherrypy import Tool +from cherrypy.process import plugins +try: + from cheroot.server import HTTPConnection, HTTPRequest, KnownLengthRFile +except ImportError: + from cherrypy.wsgiserver import HTTPConnection, HTTPRequest, KnownLengthRFile + +from ws4py import WS_KEY, WS_VERSION +from ws4py.exc import HandshakeError +from ws4py.websocket import WebSocket +from ws4py.compat import py3k, get_connection, detach_connection +from ws4py.manager import WebSocketManager + +__all__ = ['WebSocketTool', 'WebSocketPlugin'] + +class WebSocketTool(Tool): + def __init__(self): + Tool.__init__(self, 'before_request_body', self.upgrade) + + def _setup(self): + conf = self._merged_args() + hooks = cherrypy.serving.request.hooks + p = conf.pop("priority", getattr(self.callable, "priority", + self._priority)) + hooks.attach(self._point, self.callable, priority=p, **conf) + hooks.attach('before_finalize', self.complete, + priority=p) + hooks.attach('on_end_resource', self.cleanup_headers, + priority=70) + hooks.attach('on_end_request', self.start_handler, + priority=70) + + def upgrade(self, protocols=None, extensions=None, version=WS_VERSION, + handler_cls=WebSocket, heartbeat_freq=None): + """ + Performs the upgrade of the connection to the WebSocket + protocol. + + The provided protocols may be a list of WebSocket + protocols supported by the instance of the tool. + + When no list is provided and no protocol is either + during the upgrade, then the protocol parameter is + not taken into account. On the other hand, + if the protocol from the handshake isn't part + of the provided list, the upgrade fails immediatly. + """ + request = cherrypy.serving.request + request.process_request_body = False + + ws_protocols = None + ws_location = None + ws_version = version + ws_key = None + ws_extensions = [] + + if request.method != 'GET': + raise HandshakeError('HTTP method must be a GET') + + for key, expected_value in [('Upgrade', 'websocket'), + ('Connection', 'upgrade')]: + actual_value = request.headers.get(key, '').lower() + if not actual_value: + raise HandshakeError('Header %s is not defined' % key) + if expected_value not in actual_value: + raise HandshakeError('Illegal value for header %s: %s' % + (key, actual_value)) + + version = request.headers.get('Sec-WebSocket-Version') + supported_versions = ', '.join([str(v) for v in ws_version]) + version_is_valid = False + if version: + try: version = int(version) + except: pass + else: version_is_valid = version in ws_version + + if not version_is_valid: + cherrypy.response.headers['Sec-WebSocket-Version'] = supported_versions + raise HandshakeError('Unhandled or missing WebSocket version') + + key = request.headers.get('Sec-WebSocket-Key') + if key: + ws_key = base64.b64decode(key.encode('utf-8')) + if len(ws_key) != 16: + raise HandshakeError("WebSocket key's length is invalid") + + protocols = protocols or [] + subprotocols = request.headers.get('Sec-WebSocket-Protocol') + if subprotocols: + ws_protocols = [] + for s in subprotocols.split(','): + s = s.strip() + if s in protocols: + ws_protocols.append(s) + + exts = extensions or [] + extensions = request.headers.get('Sec-WebSocket-Extensions') + if extensions: + for ext in extensions.split(','): + ext = ext.strip() + if ext in exts: + ws_extensions.append(ext) + + location = [] + include_port = False + if request.scheme == "https": + location.append("wss://") + include_port = request.local.port != 443 + else: + location.append("ws://") + include_port = request.local.port != 80 + location.append('localhost') + if include_port: + location.append(":%d" % request.local.port) + location.append(request.path_info) + if request.query_string != "": + location.append("?%s" % request.query_string) + ws_location = ''.join(location) + + response = cherrypy.serving.response + response.stream = True + response.status = '101 Switching Protocols' + response.headers['Content-Type'] = 'text/plain' + response.headers['Upgrade'] = 'websocket' + response.headers['Connection'] = 'Upgrade' + response.headers['Sec-WebSocket-Version'] = str(version) + response.headers['Sec-WebSocket-Accept'] = base64.b64encode(sha1(key.encode('utf-8') + WS_KEY).digest()) + if ws_protocols: + response.headers['Sec-WebSocket-Protocol'] = ', '.join(ws_protocols) + if ws_extensions: + response.headers['Sec-WebSocket-Extensions'] = ','.join(ws_extensions) + + addr = (request.remote.ip, request.remote.port) + rfile = request.rfile.rfile + if isinstance(rfile, KnownLengthRFile): + rfile = rfile.rfile + + ws_conn = get_connection(rfile) + request.ws_handler = handler_cls(ws_conn, ws_protocols, ws_extensions, + request.wsgi_environ.copy(), + heartbeat_freq=heartbeat_freq) + + def complete(self): + """ + Sets some internal flags of CherryPy so that it + doesn't close the socket down. + """ + self._set_internal_flags() + + def cleanup_headers(self): + """ + Some clients aren't that smart when it comes to + headers lookup. + """ + response = cherrypy.response + if not response.header_list: + return + + headers = response.header_list[:] + for (k, v) in headers: + if k[:7] == 'Sec-Web': + response.header_list.remove((k, v)) + response.header_list.append((k.replace('Sec-Websocket', 'Sec-WebSocket'), v)) + + def start_handler(self): + """ + Runs at the end of the request processing by calling + the opened method of the handler. + """ + request = cherrypy.request + if not hasattr(request, 'ws_handler'): + return + + addr = (request.remote.ip, request.remote.port) + ws_handler = request.ws_handler + request.ws_handler = None + delattr(request, 'ws_handler') + + # By doing this we detach the socket from + # the CherryPy stack avoiding memory leaks + detach_connection(request.rfile.rfile) + + cherrypy.engine.publish('handle-websocket', ws_handler, addr) + + def _set_internal_flags(self): + """ + CherryPy has two internal flags that we are interested in + to enable WebSocket within the server. They can't be set via + a public API and considering I'd want to make this extension + as compatible as possible whilst refraining in exposing more + than should be within CherryPy, I prefer performing a bit + of introspection to set those flags. Even by Python standards + such introspection isn't the cleanest but it works well + enough in this case. + + This also means that we do that only on WebSocket + connections rather than globally and therefore we do not + harm the rest of the HTTP server. + """ + current = inspect.currentframe() + while True: + if not current: + break + _locals = current.f_locals + if 'self' in _locals: + if isinstance(_locals['self'], HTTPRequest): + _locals['self'].close_connection = True + if isinstance(_locals['self'], HTTPConnection): + _locals['self'].linger = True + # HTTPConnection is more inner than + # HTTPRequest so we can leave once + # we're done here + return + _locals = None + current = current.f_back + +class WebSocketPlugin(plugins.SimplePlugin): + def __init__(self, bus): + plugins.SimplePlugin.__init__(self, bus) + self.manager = WebSocketManager() + + def start(self): + self.bus.log("Starting WebSocket processing") + self.bus.subscribe('stop', self.cleanup) + self.bus.subscribe('handle-websocket', self.handle) + self.bus.subscribe('websocket-broadcast', self.broadcast) + self.manager.start() + + def stop(self): + self.bus.log("Terminating WebSocket processing") + self.bus.unsubscribe('stop', self.cleanup) + self.bus.unsubscribe('handle-websocket', self.handle) + self.bus.unsubscribe('websocket-broadcast', self.broadcast) + + def handle(self, ws_handler, peer_addr): + """ + Tracks the provided handler. + + :param ws_handler: websocket handler instance + :param peer_addr: remote peer address for tracing purpose + """ + self.manager.add(ws_handler) + + def cleanup(self): + """ + Terminate all connections and clear the pool. Executed when the engine stops. + """ + self.manager.close_all() + self.manager.stop() + self.manager.join() + + def broadcast(self, message, binary=False): + """ + Broadcasts a message to all connected clients known to + the server. + + :param message: a message suitable to pass to the send() method + of the connected handler. + :param binary: whether or not the message is a binary one + """ + self.manager.broadcast(message, binary) + +if __name__ == '__main__': + import random + cherrypy.config.update({'server.socket_host': '127.0.0.1', + 'server.socket_port': 9000}) + WebSocketPlugin(cherrypy.engine).subscribe() + cherrypy.tools.websocket = WebSocketTool() + + class Root(object): + @cherrypy.expose + @cherrypy.tools.websocket(on=False) + def ws(self): + return """<html> + <head> + <script type='application/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js'> </script> + <script type='application/javascript'> + $(document).ready(function() { + var ws = new WebSocket('ws://192.168.0.10:9000/'); + ws.onmessage = function (evt) { + $('#chat').val($('#chat').val() + evt.data + '\\n'); + }; + ws.onopen = function() { + ws.send("Hello there"); + }; + ws.onclose = function(evt) { + $('#chat').val($('#chat').val() + 'Connection closed by server: ' + evt.code + ' \"' + evt.reason + '\"\\n'); + }; + $('#chatform').submit(function() { + ws.send('%(username)s: ' + $('#message').val()); + $('#message').val(""); + return false; + }); + }); + </script> + </head> + <body> + <form action='/echo' id='chatform' method='get'> + <textarea id='chat' cols='35' rows='10'></textarea> + <br /> + <label for='message'>%(username)s: </label><input type='text' id='message' /> + <input type='submit' value='Send' /> + </form> + </body> + </html> + """ % {'username': "User%d" % random.randint(0, 100)} + + @cherrypy.expose + def index(self): + cherrypy.log("Handler created: %s" % repr(cherrypy.request.ws_handler)) + + cherrypy.quickstart(Root(), '/', config={'/': {'tools.websocket.on': True, + 'tools.websocket.handler_cls': EchoWebSocketHandler}}) diff --git a/game/python-extra/ws4py/server/geventserver.py b/game/python-extra/ws4py/server/geventserver.py new file mode 100644 index 0000000..13b5554 --- /dev/null +++ b/game/python-extra/ws4py/server/geventserver.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +__doc__ = """ +WSGI entities to support WebSocket from within gevent. + +Its usage is rather simple: + +.. code-block: python + + from gevent import monkey; monkey.patch_all() + from ws4py.websocket import EchoWebSocket + from ws4py.server.geventserver import WSGIServer + from ws4py.server.wsgiutils import WebSocketWSGIApplication + + server = WSGIServer(('localhost', 9000), WebSocketWSGIApplication(handler_cls=EchoWebSocket)) + server.serve_forever() + +""" +import logging + +import gevent +from gevent.pywsgi import WSGIHandler, WSGIServer as _WSGIServer +from gevent.pool import Pool + +from ws4py import format_addresses +from ws4py.server.wsgiutils import WebSocketWSGIApplication + + +logger = logging.getLogger('ws4py') + +__all__ = ['WebSocketWSGIHandler', 'WSGIServer', + 'GEventWebSocketPool'] + + +class WebSocketWSGIHandler(WSGIHandler): + """ + A WSGI handler that will perform the :rfc:`6455` + upgrade and handshake before calling the WSGI application. + + If the incoming request doesn't have a `'Upgrade'` header, + the handler will simply fallback to the gevent builtin's handler + and process it as per usual. + """ + + def run_application(self): + upgrade_header = self.environ.get('HTTP_UPGRADE', '').lower() + if upgrade_header: + # Build and start the HTTP response + self.environ['ws4py.socket'] = self.socket or self.environ['wsgi.input'].rfile._sock + self.result = self.application(self.environ, self.start_response) or [] + self.process_result() + del self.environ['ws4py.socket'] + self.socket = None + self.rfile.close() + + ws = self.environ.pop('ws4py.websocket', None) + if ws: + ws_greenlet = self.server.pool.track(ws) + # issue #170 + # in gevent 1.1 socket will be closed once application returns + # so let's wait for websocket handler to finish + ws_greenlet.join() + else: + gevent.pywsgi.WSGIHandler.run_application(self) + + +class GEventWebSocketPool(Pool): + """ + Simple pool of bound websockets. + Internally it uses a gevent group to track + the websockets. The server should call the ``clear`` + method to initiate the closing handshake when the + server is shutdown. + """ + + def track(self, websocket): + logger.info("Managing websocket %s" % format_addresses(websocket)) + return self.spawn(websocket.run) + + def clear(self): + logger.info("Terminating server and all connected websockets") + for greenlet in list(self): + try: + websocket = greenlet._run.im_self + if websocket: + websocket.close(1001, 'Server is shutting down') + except: + pass + finally: + self.discard(greenlet) + + +class WSGIServer(_WSGIServer): + handler_class = WebSocketWSGIHandler + + def __init__(self, *args, **kwargs): + """ + WSGI server that simply tracks websockets + and send them a proper closing handshake + when the server terminates. + + Other than that, the server is the same + as its :class:`gevent.pywsgi.WSGIServer` + base. + """ + _WSGIServer.__init__(self, *args, **kwargs) + self.pool = GEventWebSocketPool() + + def stop(self, *args, **kwargs): + self.pool.clear() + _WSGIServer.stop(self, *args, **kwargs) + + +if __name__ == '__main__': + + from ws4py import configure_logger + configure_logger() + + from ws4py.websocket import EchoWebSocket + server = WSGIServer(('127.0.0.1', 9000), + WebSocketWSGIApplication(handler_cls=EchoWebSocket)) + server.serve_forever() diff --git a/game/python-extra/ws4py/server/tulipserver.py b/game/python-extra/ws4py/server/tulipserver.py new file mode 100644 index 0000000..2786c16 --- /dev/null +++ b/game/python-extra/ws4py/server/tulipserver.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +import base64 +from hashlib import sha1 +from email.parser import BytesHeaderParser +import io + +import asyncio + +from ws4py import WS_KEY, WS_VERSION +from ws4py.exc import HandshakeError +from ws4py.websocket import WebSocket + +LF = b'\n' +CRLF = b'\r\n' +SPACE = b' ' +EMPTY = b'' + +__all__ = ['WebSocketProtocol'] + +class WebSocketProtocol(asyncio.StreamReaderProtocol): + def __init__(self, handler_cls): + asyncio.StreamReaderProtocol.__init__(self, asyncio.StreamReader(), + self._pseudo_connected) + self.ws = handler_cls(self) + + def _pseudo_connected(self, reader, writer): + pass + + def connection_made(self, transport): + """ + A peer is now connected and we receive an instance + of the underlying :class:`asyncio.Transport`. + + We :class:`asyncio.StreamReader` is created + and the transport is associated before the + initial HTTP handshake is undertaken. + """ + #self.transport = transport + #self.stream = asyncio.StreamReader() + #self.stream.set_transport(transport) + asyncio.StreamReaderProtocol.connection_made(self, transport) + # Let make it concurrent for others to tag along + f = asyncio.async(self.handle_initial_handshake()) + f.add_done_callback(self.terminated) + + @property + def writer(self): + return self._stream_writer + + @property + def reader(self): + return self._stream_reader + + def terminated(self, f): + if f.done() and not f.cancelled(): + ex = f.exception() + if ex: + response = [b'HTTP/1.0 400 Bad Request'] + response.append(b'Content-Length: 0') + response.append(b'Connection: close') + response.append(b'') + response.append(b'') + self.writer.write(CRLF.join(response)) + self.ws.close_connection() + + def close(self): + """ + Initiate the websocket closing handshake + which will eventuall lead to the underlying + transport. + """ + self.ws.close() + + def timeout(self): + self.ws.close_connection() + if self.ws.started: + self.ws.closed(1002, "Peer connection timed-out") + + def connection_lost(self, exc): + """ + The peer connection is now, the closing + handshake won't work so let's not even try. + However let's make the websocket handler + be aware of it by calling its `closed` + method. + """ + if exc is not None: + self.ws.close_connection() + if self.ws.started: + self.ws.closed(1002, "Peer connection was lost") + + @asyncio.coroutine + def handle_initial_handshake(self): + """ + Performs the HTTP handshake described in :rfc:`6455`. Note that + this implementation is really basic and it is strongly advised + against using it in production. It would probably break for + most clients. If you want a better support for HTTP, please + use a more reliable HTTP server implemented using asyncio. + """ + request_line = yield from self.next_line() + method, uri, req_protocol = request_line.strip().split(SPACE, 2) + + # GET required + if method.upper() != b'GET': + raise HandshakeError('HTTP method must be a GET') + + headers = yield from self.read_headers() + if req_protocol == b'HTTP/1.1' and 'Host' not in headers: + raise ValueError("Missing host header") + + for key, expected_value in [('Upgrade', 'websocket'), + ('Connection', 'upgrade')]: + actual_value = headers.get(key, '').lower() + if not actual_value: + raise HandshakeError('Header %s is not defined' % str(key)) + if expected_value not in actual_value: + raise HandshakeError('Illegal value for header %s: %s' % + (key, actual_value)) + + response_headers = {} + + ws_version = WS_VERSION + version = headers.get('Sec-WebSocket-Version') + supported_versions = ', '.join([str(v) for v in ws_version]) + version_is_valid = False + if version: + try: version = int(version) + except: pass + else: version_is_valid = version in ws_version + + if not version_is_valid: + response_headers['Sec-WebSocket-Version'] = supported_versions + raise HandshakeError('Unhandled or missing WebSocket version') + + key = headers.get('Sec-WebSocket-Key') + if key: + ws_key = base64.b64decode(key.encode('utf-8')) + if len(ws_key) != 16: + raise HandshakeError("WebSocket key's length is invalid") + + protocols = [] + ws_protocols = [] + subprotocols = headers.get('Sec-WebSocket-Protocol') + if subprotocols: + for s in subprotocols.split(','): + s = s.strip() + if s in protocols: + ws_protocols.append(s) + + exts = [] + ws_extensions = [] + extensions = headers.get('Sec-WebSocket-Extensions') + if extensions: + for ext in extensions.split(','): + ext = ext.strip() + if ext in exts: + ws_extensions.append(ext) + + self.ws.protocols = ws_protocols + self.ws.extensions = ws_extensions + self.ws.headers = headers + + response = [req_protocol + b' 101 Switching Protocols'] + response.append(b'Upgrade: websocket') + response.append(b'Content-Type: text/plain') + response.append(b'Content-Length: 0') + response.append(b'Connection: Upgrade') + response.append(b'Sec-WebSocket-Version:' + bytes(str(version), 'utf-8')) + response.append(b'Sec-WebSocket-Accept:' + base64.b64encode(sha1(key.encode('utf-8') + WS_KEY).digest())) + if ws_protocols: + response.append(b'Sec-WebSocket-Protocol:' + b', '.join(ws_protocols)) + if ws_extensions: + response.append(b'Sec-WebSocket-Extensions:' + b','.join(ws_extensions)) + response.append(b'') + response.append(b'') + self.writer.write(CRLF.join(response)) + yield from self.handle_websocket() + + @asyncio.coroutine + def handle_websocket(self): + """ + Starts the websocket process until the + exchange is completed and terminated. + """ + yield from self.ws.run() + + @asyncio.coroutine + def read_headers(self): + """ + Read all HTTP headers from the HTTP request + and returns a dictionary of them. + """ + headers = b'' + while True: + line = yield from self.next_line() + headers += line + if line == CRLF: + break + return BytesHeaderParser().parsebytes(headers) + + @asyncio.coroutine + def next_line(self): + """ + Reads data until \r\n is met and then return all read + bytes. + """ + line = yield from self.reader.readline() + if not line.endswith(CRLF): + raise ValueError("Missing mandatory trailing CRLF") + return line + +if __name__ == '__main__': + from ws4py.async_websocket import EchoWebSocket + + loop = asyncio.get_event_loop() + + def start_server(): + proto_factory = lambda: WebSocketProtocol(EchoWebSocket) + return loop.create_server(proto_factory, '', 9007) + + s = loop.run_until_complete(start_server()) + print('serving on', s.sockets[0].getsockname()) + loop.run_forever() diff --git a/game/python-extra/ws4py/server/wsgirefserver.py b/game/python-extra/ws4py/server/wsgirefserver.py new file mode 100644 index 0000000..d4a9d9a --- /dev/null +++ b/game/python-extra/ws4py/server/wsgirefserver.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +__doc__ = """ +Add WebSocket support to the built-in WSGI server +provided by the :py:mod:`wsgiref`. This is clearly not +meant to be a production server so please consider this +only for testing purpose. + +Mostly, this module overrides bits and pieces of +the built-in classes so that it supports the WebSocket +workflow. + +.. code-block:: python + + from wsgiref.simple_server import make_server + from ws4py.websocket import EchoWebSocket + from ws4py.server.wsgirefserver import WSGIServer, WebSocketWSGIRequestHandler + from ws4py.server.wsgiutils import WebSocketWSGIApplication + + server = make_server('', 9000, server_class=WSGIServer, + handler_class=WebSocketWSGIRequestHandler, + app=WebSocketWSGIApplication(handler_cls=EchoWebSocket)) + server.initialize_websockets_manager() + server.serve_forever() + +.. note:: + For some reason this server may fail against autobahntestsuite. +""" +import logging +import sys +import itertools +import operator +from wsgiref.handlers import SimpleHandler +from wsgiref.simple_server import WSGIRequestHandler, WSGIServer as _WSGIServer +from wsgiref import util + +util._hoppish = {}.__contains__ + +from ws4py.manager import WebSocketManager +from ws4py import format_addresses +from ws4py.server.wsgiutils import WebSocketWSGIApplication +from ws4py.compat import get_connection + +__all__ = ['WebSocketWSGIHandler', 'WebSocketWSGIRequestHandler', + 'WSGIServer'] + +logger = logging.getLogger('ws4py') + +class WebSocketWSGIHandler(SimpleHandler): + def setup_environ(self): + """ + Setup the environ dictionary and add the + `'ws4py.socket'` key. Its associated value + is the real socket underlying socket. + """ + SimpleHandler.setup_environ(self) + self.environ['ws4py.socket'] = get_connection(self.environ['wsgi.input']) + self.http_version = self.environ['SERVER_PROTOCOL'].rsplit('/')[-1] + + def finish_response(self): + """ + Completes the response and performs the following tasks: + + - Remove the `'ws4py.socket'` and `'ws4py.websocket'` + environ keys. + - Attach the returned websocket, if any, to the WSGI server + using its ``link_websocket_to_server`` method. + """ + # force execution of the result iterator until first actual content + rest = iter(self.result) + first = list(itertools.islice(rest, 1)) + self.result = itertools.chain(first, rest) + + # now it's safe to look if environ was modified + ws = None + if self.environ: + self.environ.pop('ws4py.socket', None) + ws = self.environ.pop('ws4py.websocket', None) + + try: + SimpleHandler.finish_response(self) + except: + if ws: + ws.close(1011, reason='Something broke') + raise + else: + if ws: + self.request_handler.server.link_websocket_to_server(ws) + +class WebSocketWSGIRequestHandler(WSGIRequestHandler): + WebSocketWSGIHandler = WebSocketWSGIHandler + def handle(self): + """ + Unfortunately the base class forces us + to override the whole method to actually provide our wsgi handler. + """ + self.raw_requestline = self.rfile.readline() + if not self.parse_request(): # An error code has been sent, just exit + return + + # next line is where we'd have expect a configuration key somehow + handler = self.WebSocketWSGIHandler( + self.rfile, self.wfile, self.get_stderr(), self.get_environ() + ) + handler.request_handler = self # backpointer for logging + handler.run(self.server.get_app()) + +class WSGIServer(_WSGIServer): + def initialize_websockets_manager(self): + """ + Call thos to start the underlying websockets + manager. Make sure to call it once your server + is created. + """ + self.manager = WebSocketManager() + self.manager.start() + + def shutdown_request(self, request): + """ + The base class would close our socket + if we didn't override it. + """ + pass + + def link_websocket_to_server(self, ws): + """ + Call this from your WSGI handler when a websocket + has been created. + """ + self.manager.add(ws) + + def server_close(self): + """ + Properly initiate closing handshakes on + all websockets when the WSGI server terminates. + """ + if hasattr(self, 'manager'): + self.manager.close_all() + self.manager.stop() + self.manager.join() + delattr(self, 'manager') + _WSGIServer.server_close(self) + +if __name__ == '__main__': + from ws4py import configure_logger + configure_logger() + + from wsgiref.simple_server import make_server + from ws4py.websocket import EchoWebSocket + + server = make_server('', 9000, server_class=WSGIServer, + handler_class=WebSocketWSGIRequestHandler, + app=WebSocketWSGIApplication(handler_cls=EchoWebSocket)) + server.initialize_websockets_manager() + try: + server.serve_forever() + except KeyboardInterrupt: + server.server_close() diff --git a/game/python-extra/ws4py/server/wsgiutils.py b/game/python-extra/ws4py/server/wsgiutils.py new file mode 100644 index 0000000..efd3242 --- /dev/null +++ b/game/python-extra/ws4py/server/wsgiutils.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +__doc__ = """ +This module provides a WSGI application suitable +for a WSGI server such as gevent or wsgiref for instance. + +:pep:`333` couldn't foresee a protocol such as +WebSockets but luckily the way the initial +protocol upgrade was designed means that we can +fit the handshake in a WSGI flow. + +The handshake validates the request against +some internal or user-provided values and +fails the request if the validation doesn't +complete. + +On success, the provided WebSocket subclass +is instanciated and stored into the +`'ws4py.websocket'` environ key so that +the WSGI server can handle it. + +The WSGI application returns an empty iterable +since there is little value to return some +content within the response to the handshake. + +A server wishing to support WebSocket via ws4py +should: + +- Provide the real socket object to ws4py through the + `'ws4py.socket'` environ key. We can't use `'wsgi.input'` + as it may be wrapper to the socket we wouldn't know + how to extract the socket from. +- Look for the `'ws4py.websocket'` key in the environ + when the application has returned and probably attach + it to a :class:`ws4py.manager.WebSocketManager` instance + so that the websocket runs its life. +- Remove the `'ws4py.websocket'` and `'ws4py.socket'` + environ keys once the application has returned. + No need for these keys to persist. +- Not close the underlying socket otherwise, well, + your websocket will also shutdown. + +.. warning:: + + The WSGI application sets the `'Upgrade'` header response + as specified by :rfc:`6455`. This is not tolerated by + :pep:`333` since it's a hop-by-hop header. + We expect most servers won't mind. +""" +import base64 +from hashlib import sha1 +import logging +import sys + +from ws4py.websocket import WebSocket +from ws4py.exc import HandshakeError +from ws4py.compat import unicode, py3k +from ws4py import WS_VERSION, WS_KEY, format_addresses + +logger = logging.getLogger('ws4py') + +__all__ = ['WebSocketWSGIApplication'] + +class WebSocketWSGIApplication(object): + def __init__(self, protocols=None, extensions=None, handler_cls=WebSocket): + """ + WSGI application usable to complete the upgrade handshake + by validating the requested protocols and extensions as + well as the websocket version. + + If the upgrade validates, the `handler_cls` class + is instanciated and stored inside the WSGI `environ` + under the `'ws4py.websocket'` key to make it + available to the WSGI handler. + """ + self.protocols = protocols + self.extensions = extensions + self.handler_cls = handler_cls + + def make_websocket(self, sock, protocols, extensions, environ): + """ + Initialize the `handler_cls` instance with the given + negociated sets of protocols and extensions as well as + the `environ` and `sock`. + + Stores then the instance in the `environ` dict + under the `'ws4py.websocket'` key. + """ + websocket = self.handler_cls(sock, protocols, extensions, + environ.copy()) + environ['ws4py.websocket'] = websocket + return websocket + + def __call__(self, environ, start_response): + if environ.get('REQUEST_METHOD') != 'GET': + raise HandshakeError('HTTP method must be a GET') + + for key, expected_value in [('HTTP_UPGRADE', 'websocket'), + ('HTTP_CONNECTION', 'upgrade')]: + actual_value = environ.get(key, '').lower() + if not actual_value: + raise HandshakeError('Header %s is not defined' % key) + if expected_value not in actual_value: + raise HandshakeError('Illegal value for header %s: %s' % + (key, actual_value)) + + key = environ.get('HTTP_SEC_WEBSOCKET_KEY') + if key: + ws_key = base64.b64decode(key.encode('utf-8')) + if len(ws_key) != 16: + raise HandshakeError("WebSocket key's length is invalid") + + version = environ.get('HTTP_SEC_WEBSOCKET_VERSION') + supported_versions = b', '.join([unicode(v).encode('utf-8') for v in WS_VERSION]) + version_is_valid = False + if version: + try: version = int(version) + except: pass + else: version_is_valid = version in WS_VERSION + + if not version_is_valid: + environ['websocket.version'] = unicode(version).encode('utf-8') + raise HandshakeError('Unhandled or missing WebSocket version') + + ws_protocols = [] + protocols = self.protocols or [] + subprotocols = environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL') + if subprotocols: + for s in subprotocols.split(','): + s = s.strip() + if s in protocols: + ws_protocols.append(s) + + ws_extensions = [] + exts = self.extensions or [] + extensions = environ.get('HTTP_SEC_WEBSOCKET_EXTENSIONS') + if extensions: + for ext in extensions.split(','): + ext = ext.strip() + if ext in exts: + ws_extensions.append(ext) + + accept_value = base64.b64encode(sha1(key.encode('utf-8') + WS_KEY).digest()) + if py3k: accept_value = accept_value.decode('utf-8') + upgrade_headers = [ + ('Upgrade', 'websocket'), + ('Connection', 'Upgrade'), + ('Sec-WebSocket-Version', '%s' % version), + ('Sec-WebSocket-Accept', accept_value), + ] + if ws_protocols: + upgrade_headers.append(('Sec-WebSocket-Protocol', ', '.join(ws_protocols))) + if ws_extensions: + upgrade_headers.append(('Sec-WebSocket-Extensions', ','.join(ws_extensions))) + + start_response("101 Switching Protocols", upgrade_headers) + + self.make_websocket(environ['ws4py.socket'], + ws_protocols, + ws_extensions, + environ) + + return [] diff --git a/game/python-extra/ws4py/streaming.py b/game/python-extra/ws4py/streaming.py new file mode 100644 index 0000000..4420ba1 --- /dev/null +++ b/game/python-extra/ws4py/streaming.py @@ -0,0 +1,319 @@ +# -*- coding: utf-8 -*- +import struct +from struct import unpack + +from ws4py.utf8validator import Utf8Validator +from ws4py.messaging import TextMessage, BinaryMessage, CloseControlMessage,\ + PingControlMessage, PongControlMessage +from ws4py.framing import Frame, OPCODE_CONTINUATION, OPCODE_TEXT, \ + OPCODE_BINARY, OPCODE_CLOSE, OPCODE_PING, OPCODE_PONG +from ws4py.exc import FrameTooLargeException, ProtocolException, InvalidBytesError,\ + TextFrameEncodingException, UnsupportedFrameTypeException, StreamClosed +from ws4py.compat import py3k + +VALID_CLOSING_CODES = [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011] + +class Stream(object): + def __init__(self, always_mask=False, expect_masking=True): + """ Represents a websocket stream of bytes flowing in and out. + + The stream doesn't know about the data provider itself and + doesn't even know about sockets. Instead the stream simply + yields for more bytes whenever it requires them. The stream owner + is responsible to provide the stream with those bytes until + a frame can be interpreted. + + .. code-block:: python + :linenos: + + >>> s = Stream() + >>> s.parser.send(BYTES) + >>> s.has_messages + False + >>> s.parser.send(MORE_BYTES) + >>> s.has_messages + True + >>> s.message + <TextMessage ... > + + Set ``always_mask`` to mask all frames built. + + Set ``expect_masking`` to indicate masking will be + checked on all parsed frames. + """ + + self.message = None + """ + Parsed test or binary messages. Whenever the parser + reads more bytes from a fragment message, those bytes + are appended to the most recent message. + """ + + self.pings = [] + """ + Parsed ping control messages. They are instances of + :class:`ws4py.messaging.PingControlMessage` + """ + + self.pongs = [] + """ + Parsed pong control messages. They are instances of + :class:`ws4py.messaging.PongControlMessage` + """ + + self.closing = None + """ + Parsed close control messsage. Instance of + :class:`ws4py.messaging.CloseControlMessage` + """ + + self.errors = [] + """ + Detected errors while parsing. Instances of + :class:`ws4py.messaging.CloseControlMessage` + """ + + self._parser = None + """ + Parser in charge to process bytes it is fed with. + """ + + self.always_mask = always_mask + self.expect_masking = expect_masking + + @property + def parser(self): + if self._parser is None: + self._parser = self.receiver() + # Python generators must be initialized once. + next(self.parser) + return self._parser + + def _cleanup(self): + """ + Frees the stream's resources rendering it unusable. + """ + self.message = None + if self._parser is not None: + if not self._parser.gi_running: + self._parser.close() + self._parser = None + self.errors = None + self.pings = None + self.pongs = None + self.closing = None + + def text_message(self, text): + """ + Returns a :class:`ws4py.messaging.TextMessage` instance + ready to be built. Convenience method so + that the caller doesn't need to import the + :class:`ws4py.messaging.TextMessage` class itself. + """ + return TextMessage(text=text) + + def binary_message(self, bytes): + """ + Returns a :class:`ws4py.messaging.BinaryMessage` instance + ready to be built. Convenience method so + that the caller doesn't need to import the + :class:`ws4py.messaging.BinaryMessage` class itself. + """ + return BinaryMessage(bytes) + + @property + def has_message(self): + """ + Checks if the stream has received any message + which, if fragmented, is now completed. + """ + if self.message is not None: + return self.message.completed + + return False + + def close(self, code=1000, reason=''): + """ + Returns a close control message built from + a :class:`ws4py.messaging.CloseControlMessage` instance, + using the given status ``code`` and ``reason`` message. + """ + return CloseControlMessage(code=code, reason=reason) + + def ping(self, data=''): + """ + Returns a ping control message built from + a :class:`ws4py.messaging.PingControlMessage` instance. + """ + return PingControlMessage(data).single(mask=self.always_mask) + + def pong(self, data=''): + """ + Returns a ping control message built from + a :class:`ws4py.messaging.PongControlMessage` instance. + """ + return PongControlMessage(data).single(mask=self.always_mask) + + def receiver(self): + """ + Parser that keeps trying to interpret bytes it is fed with as + incoming frames part of a message. + + Control message are single frames only while data messages, like text + and binary, may be fragmented accross frames. + + The way it works is by instanciating a :class:`wspy.framing.Frame` object, + then running its parser generator which yields how much bytes + it requires to performs its task. The stream parser yields this value + to its caller and feeds the frame parser. + + When the frame parser raises :exc:`StopIteration`, the stream parser + tries to make sense of the parsed frame. It dispatches the frame's bytes + to the most appropriate message type based on the frame's opcode. + + Overall this makes the stream parser totally agonstic to + the data provider. + """ + utf8validator = Utf8Validator() + running = True + frame = None + while running: + frame = Frame() + while 1: + try: + some_bytes = (yield next(frame.parser)) + frame.parser.send(some_bytes) + except GeneratorExit: + running = False + break + except StopIteration: + frame._cleanup() + some_bytes = frame.body + + # Let's avoid unmasking when there is no payload + if some_bytes: + if frame.masking_key and self.expect_masking: + some_bytes = frame.unmask(some_bytes) + elif not frame.masking_key and self.expect_masking: + msg = CloseControlMessage(code=1002, reason='Missing masking when expected') + self.errors.append(msg) + break + elif frame.masking_key and not self.expect_masking: + msg = CloseControlMessage(code=1002, reason='Masked when not expected') + self.errors.append(msg) + break + else: + # If we reach this stage, it's because + # the frame wasn't masked and we didn't expect + # it anyway. Therefore, on py2k, the bytes + # are actually a str object and can't be used + # in the utf8 validator as we need integers + # when we get each byte one by one. + # Our only solution here is to convert our + # string to a bytearray. + some_bytes = bytearray(some_bytes) + + if frame.opcode == OPCODE_TEXT: + if self.message and not self.message.completed: + # We got a text frame before we completed the previous one + msg = CloseControlMessage(code=1002, reason='Received a new message before completing previous') + self.errors.append(msg) + break + + m = TextMessage(some_bytes) + m.completed = (frame.fin == 1) + self.message = m + + if some_bytes: + is_valid, end_on_code_point, _, _ = utf8validator.validate(some_bytes) + + if not is_valid or (m.completed and not end_on_code_point): + self.errors.append(CloseControlMessage(code=1007, reason='Invalid UTF-8 bytes')) + break + + elif frame.opcode == OPCODE_BINARY: + if self.message and not self.message.completed: + # We got a text frame before we completed the previous one + msg = CloseControlMessage(code=1002, reason='Received a new message before completing previous') + self.errors.append(msg) + break + + m = BinaryMessage(some_bytes) + m.completed = (frame.fin == 1) + self.message = m + + elif frame.opcode == OPCODE_CONTINUATION: + m = self.message + if m is None: + self.errors.append(CloseControlMessage(code=1002, reason='Message not started yet')) + break + + m.extend(some_bytes) + m.completed = (frame.fin == 1) + if m.opcode == OPCODE_TEXT: + if some_bytes: + is_valid, end_on_code_point, _, _ = utf8validator.validate(some_bytes) + + if not is_valid or (m.completed and not end_on_code_point): + self.errors.append(CloseControlMessage(code=1007, reason='Invalid UTF-8 bytes')) + break + + elif frame.opcode == OPCODE_CLOSE: + code = 1005 + reason = "" + if frame.payload_length == 0: + self.closing = CloseControlMessage(code=1005) + elif frame.payload_length == 1: + self.closing = CloseControlMessage(code=1005, reason='Payload has invalid length') + else: + try: + # at this stage, some_bytes have been unmasked + # so actually are held in a bytearray + code = int(unpack("!H", bytes(some_bytes[0:2]))[0]) + except struct.error: + reason = 'Failed at decoding closing code' + else: + # Those codes are reserved or plainly forbidden + if code not in VALID_CLOSING_CODES and not (2999 < code < 5000): + reason = 'Invalid Closing Frame Code: %d' % code + code = 1005 + elif frame.payload_length > 1: + reason = some_bytes[2:] if frame.masking_key else frame.body[2:] + + if not py3k: reason = bytearray(reason) + is_valid, end_on_code_point, _, _ = utf8validator.validate(reason) + if not is_valid or not end_on_code_point: + self.errors.append(CloseControlMessage(code=1007, reason='Invalid UTF-8 bytes')) + break + reason = bytes(reason) + self.closing = CloseControlMessage(code=code, reason=reason) + + elif frame.opcode == OPCODE_PING: + self.pings.append(PingControlMessage(some_bytes)) + + elif frame.opcode == OPCODE_PONG: + self.pongs.append(PongControlMessage(some_bytes)) + + else: + self.errors.append(CloseControlMessage(code=1003)) + + break + + except ProtocolException: + self.errors.append(CloseControlMessage(code=1002)) + break + except FrameTooLargeException: + self.errors.append(CloseControlMessage(code=1002, reason="Frame was too large")) + break + + frame._cleanup() + frame.body = None + frame = None + + if self.message is not None and self.message.completed: + utf8validator.reset() + + utf8validator.reset() + utf8validator = None + + self._cleanup() diff --git a/game/python-extra/ws4py/utf8validator.py b/game/python-extra/ws4py/utf8validator.py new file mode 100644 index 0000000..50b19e5 --- /dev/null +++ b/game/python-extra/ws4py/utf8validator.py @@ -0,0 +1,117 @@ +# coding=utf-8 + +############################################################################### +## +## Copyright 2011 Tavendo GmbH +## +## 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/ +## +## 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. +## +############################################################################### + + +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/). + """ + + ## 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 + + 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. + """ + type = Utf8Validator.UTF8VALIDATOR_DFA[b] + if self.state != Utf8Validator.UTF8_ACCEPT: + self.codepoint = (b & 0x3f) | (self.codepoint << 6) + else: + self.codepoint = (0xff >> type) & b + self.state = Utf8Validator.UTF8VALIDATOR_DFA[256 + self.state * 16 + type] + return self.state + + def reset(self): + """ + Reset validator to start new incremental UTF-8 decode/validation. + """ + self.state = Utf8Validator.UTF8_ACCEPT + self.codepoint = 0 + self.i = 0 + + def validate(self, ba): + """ + Incrementally validate a chunk of bytes provided as bytearray. + + 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. + """ + state = self.state + DFA = Utf8Validator.UTF8VALIDATOR_DFA + i = 0 # make sure 'i' is set if when 'ba' is empty + for i, b in enumerate(ba): + ## optimized version of decode(), since we are not interested in actual code points + state = DFA[256 + (state << 4) + DFA[b]] + if state == Utf8Validator.UTF8_REJECT: + self.i += i + self.state = state + return False, False, i, self.i + self.i += i + self.state = state + return True, state == Utf8Validator.UTF8_ACCEPT, i, self.i diff --git a/game/python-extra/ws4py/websocket.py b/game/python-extra/ws4py/websocket.py new file mode 100644 index 0000000..f7e9e3a --- /dev/null +++ b/game/python-extra/ws4py/websocket.py @@ -0,0 +1,615 @@ +# -*- coding: utf-8 -*- +import logging +import socket +import ssl +import time +import threading +import types +import errno + +try: + from OpenSSL.SSL import Error as pyOpenSSLError +except ImportError: + class pyOpenSSLError(Exception): + pass + +from ws4py import WS_KEY, WS_VERSION +from ws4py.exc import HandshakeError, StreamClosed +from ws4py.streaming import Stream +from ws4py.messaging import Message, PingControlMessage,\ + PongControlMessage +from ws4py.compat import basestring, unicode + +DEFAULT_READING_SIZE = 2 + +logger = logging.getLogger('ws4py') + +__all__ = ['WebSocket', 'EchoWebSocket', 'Heartbeat'] + +class Heartbeat(threading.Thread): + def __init__(self, websocket, frequency=2.0): + """ + Runs at a periodic interval specified by + `frequency` by sending an unsolicitated pong + message to the connected peer. + + If the message fails to be sent and a socket + error is raised, we close the websocket + socket automatically, triggering the `closed` + handler. + """ + threading.Thread.__init__(self) + self.websocket = websocket + self.frequency = frequency + + def __enter__(self): + if self.frequency: + self.start() + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + self.stop() + + def stop(self): + self.running = False + + def run(self): + self.running = True + while self.running: + time.sleep(self.frequency) + if self.websocket.terminated: + break + + try: + self.websocket.send(PongControlMessage(data='beep')) + except socket.error: + logger.info("Heartbeat failed") + self.websocket.server_terminated = True + self.websocket.close_connection() + break + +class WebSocket(object): + """ Represents a websocket endpoint and provides a high level interface to drive the endpoint. """ + + def __init__(self, sock, protocols=None, extensions=None, environ=None, heartbeat_freq=None): + """ The ``sock`` is an opened connection + resulting from the websocket handshake. + + If ``protocols`` is provided, it is a list of protocols + negotiated during the handshake as is ``extensions``. + + If ``environ`` is provided, it is a copy of the WSGI environ + dictionnary from the underlying WSGI server. + """ + + self.stream = Stream(always_mask=False) + """ + Underlying websocket stream that performs the websocket + parsing to high level objects. By default this stream + never masks its messages. Clients using this class should + set the ``stream.always_mask`` fields to ``True`` + and ``stream.expect_masking`` fields to ``False``. + """ + + self.protocols = protocols + """ + List of protocols supported by this endpoint. + Unused for now. + """ + + self.extensions = extensions + """ + List of extensions supported by this endpoint. + Unused for now. + """ + + self.sock = sock + """ + Underlying connection. + """ + + self._is_secure = hasattr(sock, '_ssl') or hasattr(sock, '_sslobj') + """ + Tell us if the socket is secure or not. + """ + + self.client_terminated = False + """ + Indicates if the client has been marked as terminated. + """ + + self.server_terminated = False + """ + Indicates if the server has been marked as terminated. + """ + + self.reading_buffer_size = DEFAULT_READING_SIZE + """ + Current connection reading buffer size. + """ + + self.environ = environ + """ + WSGI environ dictionary. + """ + + self.heartbeat_freq = heartbeat_freq + """ + At which interval the heartbeat will be running. + Set this to `0` or `None` to disable it entirely. + """ + "Internal buffer to get around SSL problems" + self.buf = b'' + + self._local_address = None + self._peer_address = None + + ######### + # @TMW2 + # Delays the reading if we are still busy writing + self.lock = False + # @TMW2 + ######### + + + @property + def local_address(self): + """ + Local endpoint address as a tuple + """ + if not self._local_address: + self._local_address = self.sock.getsockname() + if len(self._local_address) == 4: + self._local_address = self._local_address[:2] + return self._local_address + + @property + def peer_address(self): + """ + Peer endpoint address as a tuple + """ + if not self._peer_address: + self._peer_address = self.sock.getpeername() + if len(self._peer_address) == 4: + self._peer_address = self._peer_address[:2] + return self._peer_address + + def opened(self): + """ + Called by the server when the upgrade handshake + has succeeded. + """ + pass + + def close(self, code=1000, reason=''): + """ + Call this method to initiate the websocket connection + closing by sending a close frame to the connected peer. + The ``code`` is the status code representing the + termination's reason. + + Once this method is called, the ``server_terminated`` + attribute is set. Calling this method several times is + safe as the closing frame will be sent only the first + time. + + .. seealso:: Defined Status Codes http://tools.ietf.org/html/rfc6455#section-7.4.1 + """ + if not self.server_terminated: + self.server_terminated = True + try: + self._write(self.stream.close(code=code, reason=reason).single(mask=self.stream.always_mask)) + except Exception as ex: + logger.error("Error when terminating the connection: %s", str(ex)) + + def closed(self, code, reason=None): + """ + Called when the websocket stream and connection are finally closed. + The provided ``code`` is status set by the other point and + ``reason`` is a human readable message. + + .. seealso:: Defined Status Codes http://tools.ietf.org/html/rfc6455#section-7.4.1 + """ + pass + + @property + def terminated(self): + """ + Returns ``True`` if both the client and server have been + marked as terminated. + """ + return self.client_terminated is True and self.server_terminated is True + + @property + def connection(self): + return self.sock + + def close_connection(self): + """ + Shutdowns then closes the underlying connection. + """ + if self.sock: + try: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + except: + pass + finally: + self.sock = None + + def ping(self, message): + """ + Send a ping message to the remote peer. + The given `message` must be a unicode string. + """ + self.send(PingControlMessage(message)) + + def ponged(self, pong): + """ + Pong message, as a :class:`messaging.PongControlMessage` instance, + received on the stream. + """ + pass + + def received_message(self, message): + """ + Called whenever a complete ``message``, binary or text, + is received and ready for application's processing. + + The passed message is an instance of :class:`messaging.TextMessage` + or :class:`messaging.BinaryMessage`. + + .. note:: You should override this method in your subclass. + """ + pass + + def unhandled_error(self, error): + """ + Called whenever a socket, or an OS, error is trapped + by ws4py but not managed by it. The given error is + an instance of `socket.error` or `OSError`. + + Note however that application exceptions will not go + through this handler. Instead, do make sure you + protect your code appropriately in `received_message` + or `send`. + + The default behaviour of this handler is to log + the error with a message. + """ + logger.exception("Failed to receive data") + + def _write(self, b): + """ + Trying to prevent a write operation + on an already closed websocket stream. + + This cannot be bullet proof but hopefully + will catch almost all use cases. + """ + if self.terminated or self.sock is None: + raise RuntimeError("Cannot send on a terminated websocket") + + self.sock.sendall(b) + + def send(self, payload, binary=False): + """ + Sends the given ``payload`` out. + + If ``payload`` is some bytes or a bytearray, + then it is sent as a single message not fragmented. + + If ``payload`` is a generator, each chunk is sent as part of + fragmented message. + + If ``binary`` is set, handles the payload as a binary message. + """ + ######### + # @TMW2 + # Delays the reading if we are still busy writing + while self.lock: + time.sleep(0.02) + #print("Sending data") + self.lock = True + # @TMW2 + ######### + + message_sender = self.stream.binary_message if binary else self.stream.text_message + + if isinstance(payload, basestring) or isinstance(payload, bytearray): + m = message_sender(payload).single(mask=self.stream.always_mask) + self._write(m) + + elif isinstance(payload, Message): + data = payload.single(mask=self.stream.always_mask) + self._write(data) + + elif type(payload) == types.GeneratorType: + bytes = next(payload) + first = True + for chunk in payload: + self._write(message_sender(bytes).fragment(first=first, mask=self.stream.always_mask)) + bytes = chunk + first = False + + self._write(message_sender(bytes).fragment(first=first, last=True, mask=self.stream.always_mask)) + + else: + raise ValueError("Unsupported type '%s' passed to send()" % type(payload)) + + ######### + # @TMW2 + # Delays the reading if we are still busy writing + self.lock = False + #print("Done sending, unlocked") + # @TMW2 + ######### + + def _get_from_pending(self): + """ + The SSL socket object provides the same interface + as the socket interface but behaves differently. + + When data is sent over a SSL connection + more data may be read than was requested from by + the ws4py websocket object. + + In that case, the data may have been indeed read + from the underlying real socket, but not read by the + application which will expect another trigger from the + manager's polling mechanism as if more data was still on the + wire. This will happen only when new data is + sent by the other peer which means there will be + some delay before the initial read data is handled + by the application. + + Due to this, we have to rely on a non-public method + to query the internal SSL socket buffer if it has indeed + more data pending in its buffer. + + Now, some people in the Python community + `discourage <https://bugs.python.org/issue21430>`_ + this usage of the ``pending()`` method because it's not + the right way of dealing with such use case. They advise + `this approach <https://docs.python.org/dev/library/ssl.html#notes-on-non-blocking-sockets>`_ + instead. Unfortunately, this applies only if the + application can directly control the poller which is not + the case with the WebSocket abstraction here. + + We therefore rely on this `technic <http://stackoverflow.com/questions/3187565/select-and-ssl-in-python>`_ + which seems to be valid anyway. + + This is a bit of a shame because we have to process + more data than what wanted initially. + """ + data = b"" + pending = self.sock.pending() + while pending: + data += self.sock.recv(pending) + pending = self.sock.pending() + return data + + def once(self): + """ + Performs the operation of reading from the underlying + connection in order to feed the stream of bytes. + + Because this needs to support SSL sockets, we must always + read as much as might be in the socket at any given time, + however process expects to have itself called with only a certain + number of bytes at a time. That number is found in + self.reading_buffer_size, so we read everything into our own buffer, + and then from there feed self.process. + + Then the stream indicates + whatever size must be read from the connection since + it knows the frame payload length. + + It returns `False` if an error occurred at the + socket level or during the bytes processing. Otherwise, + it returns `True`. + """ + if self.terminated: + logger.debug("WebSocket is already terminated") + return False + try: + b = b'' + if self._is_secure: + b = self._get_from_pending() + if not b and not self.buf: + b = self.sock.recv(self.reading_buffer_size) + if not b and not self.buf: + return False + self.buf += b + except (socket.error, OSError, pyOpenSSLError) as e: + if hasattr(e, "errno") and e.errno == errno.EINTR: + pass + else: + self.unhandled_error(e) + return False + else: + # process as much as we can + # the process will stop either if there is no buffer left + # or if the stream is closed + # only pass the requested number of bytes, leave the rest in the buffer + requested = self.reading_buffer_size + if not self.process(self.buf[:requested]): + return False + self.buf = self.buf[requested:] + + return True + + def terminate(self): + """ + Completes the websocket by calling the `closed` + method either using the received closing code + and reason, or when none was received, using + the special `1006` code. + + Finally close the underlying connection for + good and cleanup resources by unsetting + the `environ` and `stream` attributes. + """ + s = self.stream + + try: + if s.closing is None: + self.closed(1006, "Going away") + else: + self.closed(s.closing.code, s.closing.reason) + finally: + self.client_terminated = self.server_terminated = True + self.close_connection() + + # Cleaning up resources + s._cleanup() + self.stream = None + self.environ = None + + def process(self, bytes): + """ Takes some bytes and process them through the + internal stream's parser. If a message of any kind is + found, performs one of these actions: + + * A closing message will initiate the closing handshake + * Errors will initiate a closing handshake + * A message will be passed to the ``received_message`` method + * Pings will see pongs be sent automatically + * Pongs will be passed to the ``ponged`` method + + The process should be terminated when this method + returns ``False``. + """ + ######### + # @TMW2 + # Delays the reading if we are still busy writing + while self.lock: + time.sleep(0.02) + #print("processing, lock %d" % self.lock) + self.lock = True + #print("processing, lock %d" % self.lock) + err=1 + # @TMW2 + ######### + + s = self.stream + + if not bytes and self.reading_buffer_size > 0: + ######### + # @TMW2 + self.lock = False + # @TMW2 + ######### + return False + + ######### + # @TMW2 + while err: + try: + self.reading_buffer_size = s.parser.send(bytes) or DEFAULT_READING_SIZE + err=0 + except ValueError: + time.sleep(0.1) + #print("ValueError happened, total %d" % err) + if not err % 20: + print("ValueError happened, total %d" % err) + err+=1 + # @TMW2 + ######### + + if s.closing is not None: + logger.debug("Closing message received (%d) '%s'" % (s.closing.code, s.closing.reason)) + if not self.server_terminated: + self.close(s.closing.code, s.closing.reason) + else: + self.client_terminated = True + ######### + # @TMW2 + self.lock = False + # @TMW2 + ######### + return False + + if s.errors: + for error in s.errors: + logger.debug("Error message received (%d) '%s'" % (error.code, error.reason)) + self.close(error.code, error.reason) + s.errors = [] + ######### + # @TMW2 + self.lock = False + # @TMW2 + ######### + return False + + if s.has_message: + self.received_message(s.message) + if s.message is not None: + s.message.data = None + s.message = None + ######### + # @TMW2 + self.lock = False + # @TMW2 + ######### + return True + + if s.pings: + for ping in s.pings: + self._write(s.pong(ping.data)) + s.pings = [] + + if s.pongs: + for pong in s.pongs: + self.ponged(pong) + s.pongs = [] + + ######### + # @TMW2 + self.lock = False + # @TMW2 + ######### + return True + + def run(self): + """ + Performs the operation of reading from the underlying + connection in order to feed the stream of bytes. + + We start with a small size of two bytes to be read + from the connection so that we can quickly parse an + incoming frame header. Then the stream indicates + whatever size must be read from the connection since + it knows the frame payload length. + + Note that we perform some automatic opererations: + + * On a closing message, we respond with a closing + message and finally close the connection + * We respond to pings with pong messages. + * Whenever an error is raised by the stream parsing, + we initiate the closing of the connection with the + appropiate error code. + + This method is blocking and should likely be run + in a thread. + """ + self.sock.setblocking(True) + with Heartbeat(self, frequency=self.heartbeat_freq): + s = self.stream + + try: + self.opened() + while not self.terminated: + if not self.once(): + break + finally: + self.terminate() + +class EchoWebSocket(WebSocket): + def received_message(self, message): + """ + Automatically sends back the provided ``message`` to + its originating endpoint. + """ + self.send(message.data, message.is_binary) diff --git a/game/script.rpy b/game/script.rpy index dd3ee56..fede461 100644 --- a/game/script.rpy +++ b/game/script.rpy @@ -39,20 +39,7 @@ label start: # on_open=None, on_message=None, on_error=None, # on_close=None, on_ping=None, on_pong=None, # on_data=None): - if (persistent.ssl_enabled): - stdout(_("Opening new secure socket...")) - ws = wsock.WebSocketApp("wss://"+HOST+":61000", - on_data=ondata, on_error=onerror, on_open=onopen, - on_message=onmsg) - ws.on_open = onopen - renpy.invoke_in_thread(ws.run_forever, sslopt={"cert_reqs": CERT_NONE}) - else: - stdout(_("Opening new socket...")) - ws = wsock.WebSocketApp("ws://"+HOST+":61000", - on_data=ondata, on_error=onerror, on_open=onopen, - on_message=onmsg) - ws.on_open = onopen - renpy.invoke_in_thread(ws.run_forever) + renpy.invoke_in_thread(supervisor, persistent.ssl_enabled) except: # Enter in infinite loop, this will never resolve stdout("Unrecoverable error, the program is dead.") |