Hello community,

here is the log from the commit of package python3-ws4py for openSUSE:Factory 
checked in at 2016-06-25 02:23:49
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python3-ws4py (Old)
 and      /work/SRC/openSUSE:Factory/.python3-ws4py.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python3-ws4py"

Changes:
--------
--- /work/SRC/openSUSE:Factory/python3-ws4py/python3-ws4py.changes      
2016-05-25 21:26:53.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.python3-ws4py.new/python3-ws4py.changes 
2016-06-25 02:23:58.000000000 +0200
@@ -1,0 +2,9 @@
+Thu Jun 23 15:43:25 UTC 2016 - [email protected]
+
+- specfile:
+  * include python3-setuptools
+
+- update to version 0.3.5:
+  (no changelog available)
+
+-------------------------------------------------------------------
@@ -6 +14,0 @@
-

Old:
----
  ws4py-0.3.4.tar.gz

New:
----
  ws4py-0.3.5.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python3-ws4py.spec ++++++
--- /var/tmp/diff_new_pack.6DkLys/_old  2016-06-25 02:23:59.000000000 +0200
+++ /var/tmp/diff_new_pack.6DkLys/_new  2016-06-25 02:23:59.000000000 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python3-ws4py
-Version:        0.3.4
+Version:        0.3.5
 Release:        0
 Summary:        WebSocket for Python
 License:        BSD-2-Clause
@@ -26,6 +26,7 @@
 Source0:        
https://files.pythonhosted.org/packages/source/w/ws4py/ws4py-%{version}.tar.gz
 BuildArch:      noarch
 BuildRequires:  python3-devel
+BuildRequires:  python3-setuptools
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
 
 %description

++++++ ws4py-0.3.4.tar.gz -> ws4py-0.3.5.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/PKG-INFO new/ws4py-0.3.5/PKG-INFO
--- old/ws4py-0.3.4/PKG-INFO    2014-03-30 12:28:57.000000000 +0200
+++ new/ws4py-0.3.5/PKG-INFO    2016-06-05 21:28:30.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: ws4py
-Version: 0.3.4
+Version: 0.3.5
 Summary: WebSocket client and server library for Python 2 and 3 as well as PyPy
 Home-page: https://github.com/Lawouach/WebSocket-for-Python
 Author: Sylvain Hellegouarch
@@ -20,6 +20,7 @@
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Communications
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/setup.py new/ws4py-0.3.5/setup.py
--- old/ws4py-0.3.4/setup.py    2014-03-30 12:23:02.000000000 +0200
+++ new/ws4py-0.3.5/setup.py    2016-06-05 21:07:56.000000000 +0200
@@ -29,7 +29,7 @@
         return amended_modules
   
 setup(name = "ws4py",
-      version = '0.3.4',
+      version = '0.3.5',
       description = "WebSocket client and server library for Python 2 and 3 as 
well as PyPy",
       maintainer = "Sylvain Hellegouarch",
       maintainer_email = "[email protected]",
@@ -51,6 +51,7 @@
           'Programming Language :: Python :: 3',
           'Programming Language :: Python :: 3.3',
           'Programming Language :: Python :: 3.4',
+          'Programming Language :: Python :: 3.5',
           'Programming Language :: Python :: Implementation :: CPython',
           'Programming Language :: Python :: Implementation :: PyPy',
           'Topic :: Communications',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/test/test_cherrypy.py 
new/ws4py-0.3.5/test/test_cherrypy.py
--- old/ws4py-0.3.4/test/test_cherrypy.py       2014-01-25 22:30:19.000000000 
+0100
+++ new/ws4py-0.3.5/test/test_cherrypy.py       2016-06-05 17:59:03.000000000 
+0200
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 import os
+import socket
 import time
 import unittest
 
@@ -65,7 +66,7 @@
         manager = cherrypy.engine.websocket.manager
         self.assertEqual(len(manager), 0)
 
-        s = MagicMock()
+        s = MagicMock(spec=socket.socket)
         s.recv.return_value = Frame(opcode=OPCODE_TEXT, body=b'hello',
                                     fin=1, masking_key=os.urandom(4)).build()
         h = EchoWebSocket(s, [], [])
@@ -87,7 +88,7 @@
 
         # the poller runs a thread, give it time to get there
         time.sleep(1)
-
+        
         # TODO: Implement a fake poller so that works...
         self.assertEqual(len(manager), 0)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/test/test_client.py 
new/ws4py-0.3.5/test/test_client.py
--- old/ws4py-0.3.4/test/test_client.py 2014-01-25 22:30:07.000000000 +0100
+++ new/ws4py-0.3.5/test/test_client.py 2016-06-05 17:59:03.000000000 +0200
@@ -1,4 +1,8 @@
 # -*- coding: utf-8 -*-
+from base64 import b64encode
+from hashlib import sha1
+import os
+import socket
 import time
 import unittest
 
@@ -6,8 +10,13 @@
 
 from ws4py.manager import WebSocketManager
 from ws4py.websocket import WebSocket
+from ws4py import WS_KEY
+from ws4py.exc import HandshakeError
+from ws4py.framing import Frame, OPCODE_TEXT, OPCODE_CLOSE
+from ws4py.messaging import CloseControlMessage
 from ws4py.client import WebSocketBaseClient
-           
+from ws4py.client.threadedclient import WebSocketClient
+
 class BasicClientTest(unittest.TestCase):
     def test_invalid_hostname_in_url(self):
         self.assertRaises(ValueError, WebSocketBaseClient, 
url="qsdfqsd65qsd354")
@@ -15,11 +24,244 @@
     def test_invalid_scheme_in_url(self):
         self.assertRaises(ValueError, WebSocketBaseClient, 
url="ftp://localhost";)
     
+    def test_invalid_hostname_in_url(self):
+        self.assertRaises(ValueError, WebSocketBaseClient, url="ftp://?/";)
+    
+    def test_parse_unix_schemes(self):
+        c = WebSocketBaseClient(url="ws+unix:///my.socket")
+        self.assertEqual(c.scheme, "ws+unix")
+        self.assertEqual(c.host, "localhost")
+        self.assertIsNone(c.port)
+        self.assertEqual(c.unix_socket_path, "/my.socket")
+        self.assertEqual(c.resource, "/")
+        self.assertEqual(c.bind_addr, "/my.socket")
+        
+        c = WebSocketBaseClient(url="wss+unix:///my.socket")
+        self.assertEqual(c.scheme, "wss+unix")
+        self.assertEqual(c.host, "localhost")
+        self.assertIsNone(c.port)
+        self.assertEqual(c.unix_socket_path, "/my.socket")
+        self.assertEqual(c.resource, "/")
+        self.assertEqual(c.bind_addr, "/my.socket")
+        
+    def test_parse_ws_scheme(self):
+        c = WebSocketBaseClient(url="ws://127.0.0.1/")
+        self.assertEqual(c.scheme, "ws")
+        self.assertEqual(c.host, "127.0.0.1")
+        self.assertEqual(c.port, 80)
+        self.assertEqual(c.resource, "/")
+        self.assertEqual(c.bind_addr, ("127.0.0.1", 80))
+        
+    def test_parse_ws_scheme_when_missing_resource(self):
+        c = WebSocketBaseClient(url="ws://127.0.0.1")
+        self.assertEqual(c.scheme, "ws")
+        self.assertEqual(c.host, "127.0.0.1")
+        self.assertEqual(c.port, 80)
+        self.assertEqual(c.resource, "/")
+        self.assertEqual(c.bind_addr, ("127.0.0.1", 80))
+        
+    def test_parse_ws_scheme_with_port(self):
+        c = WebSocketBaseClient(url="ws://127.0.0.1:9090")
+        self.assertEqual(c.scheme, "ws")
+        self.assertEqual(c.host, "127.0.0.1")
+        self.assertEqual(c.port, 9090)
+        self.assertEqual(c.resource, "/")
+        self.assertEqual(c.bind_addr, ("127.0.0.1", 9090))
+        
+    def test_parse_ws_scheme_with_query_string(self):
+        c = WebSocketBaseClient(url="ws://127.0.0.1/?token=value")
+        self.assertEqual(c.scheme, "ws")
+        self.assertEqual(c.host, "127.0.0.1")
+        self.assertEqual(c.port, 80)
+        self.assertEqual(c.resource, "/?token=value")
+        self.assertEqual(c.bind_addr, ("127.0.0.1", 80))
+        
+    def test_parse_wss_scheme(self):
+        c = WebSocketBaseClient(url="wss://127.0.0.1/")
+        self.assertEqual(c.scheme, "wss")
+        self.assertEqual(c.host, "127.0.0.1")
+        self.assertEqual(c.port, 443)
+        self.assertEqual(c.resource, "/")
+        self.assertEqual(c.bind_addr, ("127.0.0.1", 443))
+        
+    def test_parse_wss_scheme_when_missing_resource(self):
+        c = WebSocketBaseClient(url="wss://127.0.0.1")
+        self.assertEqual(c.scheme, "wss")
+        self.assertEqual(c.host, "127.0.0.1")
+        self.assertEqual(c.port, 443)
+        self.assertEqual(c.resource, "/")
+        self.assertEqual(c.bind_addr, ("127.0.0.1", 443))
+        
+    def test_parse_wss_scheme_with_port(self):
+        c = WebSocketBaseClient(url="wss://127.0.0.1:9090")
+        self.assertEqual(c.scheme, "wss")
+        self.assertEqual(c.host, "127.0.0.1")
+        self.assertEqual(c.port, 9090)
+        self.assertEqual(c.resource, "/")
+        self.assertEqual(c.bind_addr, ("127.0.0.1", 9090))
+        
+    def test_parse_wss_scheme_with_query_string(self):
+        c = WebSocketBaseClient(url="wss://127.0.0.1/?token=value")
+        self.assertEqual(c.scheme, "wss")
+        self.assertEqual(c.host, "127.0.0.1")
+        self.assertEqual(c.port, 443)
+        self.assertEqual(c.resource, "/?token=value")
+        self.assertEqual(c.bind_addr, ("127.0.0.1", 443))
+        
+    @patch('ws4py.client.socket')
+    def test_connect_and_close(self, sock):
+        
+        s = MagicMock()
+        sock.socket.return_value = s
+        sock.getaddrinfo.return_value = [(socket.AF_INET, socket.SOCK_STREAM, 
0, "",
+                                          ("127.0.0.1", 80, 0, 0))]
+
+        c = WebSocketBaseClient(url="ws://127.0.0.1/?token=value")
+        
+        s.recv.return_value = b"\r\n".join([
+            b"HTTP/1.1 101 Switching Protocols",
+            b"Connection: Upgrade",
+            b"Sec-Websocket-Version: 13",
+            b"Content-Type: text/plain;charset=utf-8",
+            b"Sec-Websocket-Accept: " + b64encode(sha1(c.key + 
WS_KEY).digest()),
+            b"Upgrade: websocket",
+            b"Date: Sun, 26 Jul 2015 12:32:55 GMT",
+            b"Server: ws4py/test",
+            b"\r\n"
+        ])
+
+        c.connect()
+        s.connect.assert_called_once_with(("127.0.0.1", 80))
+
+        s.reset_mock()
+        c.close(code=1006, reason="boom")
+        args = s.sendall.call_args_list[0]
+        f = Frame()
+        f.parser.send(args[0][0])
+        f.parser.close()
+        self.assertIn(b'boom', f.unmask(f.body))
+        
+    @patch('ws4py.client.socket')
+    def test_empty_response(self, sock):
+        
+        s = MagicMock()
+        sock.socket.return_value = s
+        sock.getaddrinfo.return_value = [(socket.AF_INET, socket.SOCK_STREAM, 
0, "",
+                                          ("127.0.0.1", 80, 0, 0))]
+
+        c = WebSocketBaseClient(url="ws://127.0.0.1/?token=value")
+        
+        s.recv.return_value = b""
+        self.assertRaises(HandshakeError, c.connect)
+        s.shutdown.assert_called_once_with(socket.SHUT_RDWR)
+        s.close.assert_called_once_with()
+        
+    @patch('ws4py.client.socket')
+    def test_invdalid_response_code(self, sock):
+        
+        s = MagicMock()
+        sock.socket.return_value = s
+        sock.getaddrinfo.return_value = [(socket.AF_INET, socket.SOCK_STREAM, 
0, "",
+                                          ("127.0.0.1", 80, 0, 0))]
+
+        c = WebSocketBaseClient(url="ws://127.0.0.1/?token=value")
+        
+        s.recv.return_value = b"\r\n".join([
+            b"HTTP/1.1 200 Switching Protocols",
+            b"Connection: Upgrade",
+            b"Sec-Websocket-Version: 13",
+            b"Content-Type: text/plain;charset=utf-8",
+            b"Sec-Websocket-Accept: " + b64encode(sha1(c.key + 
WS_KEY).digest()),
+            b"Upgrade: websocket",
+            b"Date: Sun, 26 Jul 2015 12:32:55 GMT",
+            b"Server: ws4py/test",
+            b"\r\n"
+        ])
+
+        self.assertRaises(HandshakeError, c.connect)
+        s.shutdown.assert_called_once_with(socket.SHUT_RDWR)
+        s.close.assert_called_once_with()
+        
+    @patch('ws4py.client.socket')
+    def test_invalid_response_headers(self, sock):
+        
+        for key_header, invalid_value in ((b'upgrade', b'boom'),
+                                          (b'connection', b'bim')):
+            s = MagicMock()
+            sock.socket.return_value = s
+            sock.getaddrinfo.return_value = [(socket.AF_INET, 
socket.SOCK_STREAM, 0, "",
+                                              ("127.0.0.1", 80, 0, 0))]
+            c = WebSocketBaseClient(url="ws://127.0.0.1/?token=value")
+            
+            status_line = b"HTTP/1.1 101 Switching Protocols"
+            headers = {
+                b"connection": b"Upgrade",
+                b"Sec-Websocket-Version": b"13",
+                b"Content-Type": b"text/plain;charset=utf-8",
+                b"Sec-Websocket-Accept": b64encode(sha1(c.key + 
WS_KEY).digest()),
+                b"upgrade": b"websocket",
+                b"Date": b"Sun, 26 Jul 2015 12:32:55 GMT",
+                b"Server": b"ws4py/test"
+            }
+
+            headers[key_header] = invalid_value
+            
+            request = [status_line] + [k + b" : " + v for (k, v) in 
headers.items()] + [b'\r\n']
+            s.recv.return_value = b"\r\n".join(request)
+
+            self.assertRaises(HandshakeError, c.connect)
+            s.shutdown.assert_called_once_with(socket.SHUT_RDWR)
+            s.close.assert_called_once_with()
+            sock.reset_mock()
+        
+class ThreadedClientTest(unittest.TestCase):
+    @patch('ws4py.client.socket')
+    def test_thread_is_started_once_connected(self, sock):
+        s = MagicMock(spec=socket.socket)
+        sock.socket.return_value = s
+        sock.getaddrinfo.return_value = [(socket.AF_INET, socket.SOCK_STREAM, 
0, "",
+                                          ("127.0.0.1", 80, 0, 0))]
+ 
+        c = WebSocketClient(url="ws://127.0.0.1/")
+
+        def exchange1(*args, **kwargs):
+            yield b"\r\n".join([
+                    b"HTTP/1.1 101 Switching Protocols",
+                    b"Connection: Upgrade",
+                    b"Sec-Websocket-Version: 13",
+                    b"Content-Type: text/plain;charset=utf-8",
+                    b"Sec-Websocket-Accept: " + b64encode(sha1(c.key + 
WS_KEY).digest()),
+                    b"Upgrade: websocket",
+                    b"Date: Sun, 26 Jul 2015 12:32:55 GMT",
+                    b"Server: ws4py/test",
+                    b"\r\n"
+                ])
+
+            for i in range(100):
+                time.sleep(0.1)
+                yield Frame(opcode=OPCODE_TEXT, body=b'hello',
+                            fin=1).build()
+
+        s.recv.side_effect = exchange1()
+        self.assertFalse(c._th.is_alive())
+        
+        c.connect()
+        time.sleep(0.5)
+        self.assertTrue(c._th.is_alive())
+
+        def exchange2(*args, **kwargs):
+            yield Frame(opcode=OPCODE_CLOSE, body=b'',
+                        fin=1).build()
+        s.recv.side_effect = exchange2()
+        time.sleep(0.5)
+        self.assertFalse(c._th.is_alive())
+        
     
 if __name__ == '__main__':
     suite = unittest.TestSuite()
     loader = unittest.TestLoader()
-    for testcase in [BasicClientTest]:
+    for testcase in [BasicClientTest,
+                     ThreadedClientTest]:
         tests = loader.loadTestsFromTestCase(testcase)
         suite.addTests(tests)
     unittest.TextTestRunner(verbosity=2).run(suite)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/test/test_frame.py 
new/ws4py-0.3.5/test/test_frame.py
--- old/ws4py-0.3.4/test/test_frame.py  2014-03-29 13:54:10.000000000 +0100
+++ new/ws4py-0.3.5/test/test_frame.py  2016-06-05 17:59:03.000000000 +0200
@@ -3,6 +3,7 @@
 import unittest
 import types
 import random
+from struct import pack, unpack
 
 from ws4py.framing import Frame, \
      OPCODE_CONTINUATION, OPCODE_TEXT, \
@@ -238,6 +239,31 @@
         f.parser.send(bytes[12:])
         self.assertEqual(f.unmask(f.body), body)
         
+    def test_frame_too_large_with_7_bit_length(self):
+        header = pack('!B', ((1 << 7)
+                             | (0 << 6)
+                             | (0 << 5)
+                             | (0 << 4)
+                             | OPCODE_TEXT))
+        header += pack('!B', 127) + pack('!Q', 1 << 63)
+        b = bytes(header + b'')
+        f = Frame()
+        self.assertRaises(FrameTooLargeException, f.parser.send, b)
+
+    def test_not_sensitive_to_overflow(self):
+        header = pack('!B', ((1 << 7)
+                             | (0 << 6)
+                             | (0 << 5)
+                             | (0 << 4)
+                             | OPCODE_TEXT))
+        header += pack('!B', 126) + pack('!H', 256)
+        b = bytes(header + b'*' * 512)
+        f = Frame()
+        f.parser.send(b)
+        # even though we tried to inject 512 bytes, we
+        # still only read 256
+        self.assertEqual(len(f.body), 256)
+        
     def test_frame_sized_126(self):
         body = b'*'*256
         bytes = Frame(opcode=OPCODE_TEXT, body=body, fin=1).build()
@@ -271,7 +297,7 @@
         # parse the rest of our data
         f.parser.send(bytes[10:])
         self.assertEqual(f.body, body)
-        
+
 if __name__ == '__main__':
     suite = unittest.TestSuite()
     loader = unittest.TestLoader()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/test/test_logger.py 
new/ws4py-0.3.5/test/test_logger.py
--- old/ws4py-0.3.4/test/test_logger.py 1970-01-01 01:00:00.000000000 +0100
+++ new/ws4py-0.3.5/test/test_logger.py 2016-06-05 17:59:03.000000000 +0200
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+import logging 
+import logging.handlers as handlers
+import os, os.path
+import unittest
+import sys
+
+from ws4py import configure_logger
+
+def clean_logger():
+    logger = logging.getLogger('ws4py')
+    for handler in list(logger.handlers):
+        try:
+            handler.close()
+        except KeyError:
+            pass
+        logger.removeHandler(handler)
+            
+class WSTestLogger(unittest.TestCase):
+    def tearDown(self):
+        clean_logger()
+        
+    def test_named_logger(self):
+        logger = configure_logger(stdout=False, filepath='./my.log')
+
+        logger = logging.getLogger('ws4py')
+        self.assertEqual(logger.getEffectiveLevel(), logging.INFO)
+        
+    def test_level(self):
+        logger = configure_logger(stdout=True, filepath='./my.log',
+                                  level=logging.DEBUG)
+
+        self.assertEqual(logger.getEffectiveLevel(), logging.DEBUG)
+        for handler in logger.handlers:
+            self.assertEqual(handler.level, logging.DEBUG)
+        
+    def test_file_logger(self):
+        filepath = os.path.abspath('./my.log')
+        logger = configure_logger(stdout=False, filepath=filepath)
+        for handler in logger.handlers:
+            if isinstance(handler, handlers.RotatingFileHandler):
+                self.assertEqual(handler.baseFilename, filepath)
+                self.assertEqual(handler.stream.name, filepath)
+                break
+        else:
+            self.fail("File logger not configured")
+
+    def test_stdout_logger(self):
+        logger = configure_logger()
+        for handler in logger.handlers:
+            if isinstance(handler, logging.StreamHandler) and not\
+              isinstance(handler, handlers.RotatingFileHandler):
+                self.assertTrue(handler.stream is sys.stdout)
+                break
+        else:
+            self.fail("Stream logger not configured")
+
+if __name__ == '__main__':
+    suite = unittest.TestSuite()
+    loader = unittest.TestLoader()
+    for testcase in [WSTestLogger]:
+        tests = loader.loadTestsFromTestCase(testcase)
+        suite.addTests(tests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/test/test_manager.py 
new/ws4py-0.3.5/test/test_manager.py
--- old/ws4py-0.3.4/test/test_manager.py        2014-01-25 22:30:07.000000000 
+0100
+++ new/ws4py-0.3.5/test/test_manager.py        2016-06-05 17:59:03.000000000 
+0200
@@ -1,10 +1,17 @@
 # -*- coding: utf-8 -*-
 import time
+import itertools
 import unittest
 
+try:
+    from itertools import izip_longest as zip_longest
+except ImportError:
+    from itertools import zip_longest
+
 from mock import MagicMock, call, patch
 
-from ws4py.manager import WebSocketManager
+from ws4py.manager import WebSocketManager, SelectPoller,\
+     EPollPoller
 from ws4py.websocket import WebSocket
 
 class WSManagerTest(unittest.TestCase):
@@ -16,10 +23,10 @@
         ws.sock.fileno.return_value = 1
         
         m.add(ws)
-        m.poller.register.assert_call_once_with(ws)
+        m.poller.register.assert_called_once_with(1)
 
         m.remove(ws)
-        m.poller.unregister.assert_call_once_with(ws)
+        m.poller.unregister.assert_called_once_with(1)
         
     @patch('ws4py.manager.SelectPoller')
     def test_cannot_add_websocket_more_than_once(self, MockSelectPoller):
@@ -49,7 +56,7 @@
         self.assertEqual(len(m), 1)
         m.remove(ws)
         self.assertEqual(len(m), 0)
-        m.poller.unregister.assert_call_once_with(ws)
+        m.poller.unregister.assert_called_once_with(1)
         m.poller.reset_mock()
         
         m.remove(ws)
@@ -97,8 +104,9 @@
         
         m.add(ws)
         m.start()
+        time.sleep(0.2)
         
-        ws.terminate.assert_call_once_with()
+        ws.terminate.assert_called_once_with()
         
         m.stop()
     
@@ -109,7 +117,7 @@
         ws = MagicMock()
         m.add(ws)
         m.close_all()
-        ws.terminate.assert_call_once_with(1001, 'Server is shutting down')
+        ws.close.assert_called_once_with(code=1001, reason='Server is shutting 
down')
         
     @patch('ws4py.manager.SelectPoller')
     def test_broadcast(self, MockSelectPoller):
@@ -120,7 +128,7 @@
         m.add(ws)
 
         m.broadcast(b'hello there')
-        ws.send.assert_call_once_with(b'hello there')
+        ws.send.assert_called_once_with(b'hello there', False)
         
     @patch('ws4py.manager.SelectPoller')
     def test_broadcast_failure_must_not_break_caller(self, MockSelectPoller):
@@ -135,11 +143,144 @@
                 m.broadcast(b'hello there')
         except:
                 self.fail("Broadcasting shouldn't have failed")
-                
+
+class WSSelectPollerTest(unittest.TestCase):
+    @patch('ws4py.manager.select')
+    def test_release_poller(self, select):
+        poller = SelectPoller()
+        select.select.return_value = (poller._fds, None, None)
+        
+        poller.register(0)
+        poller.register(1)
+        fd = poller.poll()
+        self.assertEqual(fd, [0, 1])
+
+        poller.unregister(0)
+        fd = poller.poll()
+        self.assertEqual(fd, [1])
+        
+        poller.release()
+        fd = poller.poll()
+        self.assertEqual(fd, [])
+         
+    def test_timeout_when_no_registered_fds(self):
+        poller = SelectPoller(timeout=0.5)
+        a = time.time()
+        fd = poller.poll()
+        b = time.time()
+        self.assertEqual(fd, [])
+
+        d = b - a 
+        if not (0.48 < d < 0.52):
+            self.fail("Did not wait for the appropriate amount of time: %f" % 
d)
+
+    @patch('ws4py.manager.select')
+    def test_register_twice_does_not_duplicate_fd(self, select):
+        poller = SelectPoller()
+        select.select.return_value = (poller._fds, None, None)
+        poller.register(0)
+        poller.register(0)
+        fd = poller.poll()
+        self.assertEqual(fd, [0])
+            
+    def test_unregister_twice_has_no_side_effect(self):
+        poller = SelectPoller()
+        poller.register(0)
+        poller.unregister(0)
+        try:
+            poller.unregister(0)
+        except Exception as ex:
+            self.fail("Shouldn't have failed: %s" % ex)
+            
+     
+class FakeEpoll(object):
+    def __init__(self):
+        self.fds = []
+
+    def close(self):
+        self.fds = []
+
+    def register(self, fd, mask):
+        if fd in self.fds:
+            raise IOError("Already registered")
+
+        self.fds.append(fd)
+
+    def unregister(self, fd):
+        # epoll's documentation doesn't say anything
+        # about removing fds that are not registered
+        # yet/any longer
+        if fd in self.fds:
+            self.fds.remove(fd)
+
+    def poll(self, timeout):
+        # this isn't really what's happening
+        # inside but we're not testing
+        # epoll afterall
+        if not self.fds:
+            time.sleep(timeout)
+            return []
+
+        # fake EPOLLIN
+        return zip_longest(self.fds, '', fillvalue=1)
+        
+class WSEPollPollerTest(unittest.TestCase):
+    @patch('ws4py.manager.select')
+    def test_release_poller(self, select):
+        select.epoll.return_value = FakeEpoll()
+        poller = EPollPoller()
+        
+        poller.register(0)
+        poller.register(1)
+        fd = poller.poll()
+        self.assertEqual(list(fd), [0, 1])
+
+        poller.unregister(0)
+        fd = poller.poll()
+        self.assertEqual(list(fd), [1])
+        
+        poller.release()
+        fd = poller.poll()
+        self.assertEqual(list(fd), [])
+         
+    @patch('ws4py.manager.select')
+    def test_timeout_when_no_registered_fds(self, select):
+        select.epoll.return_value = FakeEpoll()
+        poller = EPollPoller(timeout=0.5)
+        
+        a = time.time()
+        fd = list(poller.poll())
+        b = time.time()
+        self.assertEqual(fd, [])
+
+        d = b - a
+        if not (0.48 < d < 0.52):
+            self.fail("Did not wait for the appropriate amount of time: %f" % 
d)
+
+    @patch('ws4py.manager.select')
+    def test_register_twice_does_not_duplicate_fd(self, select):
+        select.epoll.return_value = FakeEpoll()
+        poller = EPollPoller()
+        poller.register(0)
+        poller.register(0)  # IOError is swallowed
+        fd = poller.poll()
+        self.assertEqual(list(fd), [0])
+            
+    @patch('ws4py.manager.select')
+    def test_unregister_twice_has_no_side_effect(self, select):
+        select.epoll.return_value = FakeEpoll()
+        poller = EPollPoller()
+        poller.register(0)
+        poller.unregister(0)
+        try:
+            poller.unregister(0)
+        except Exception as ex:
+            self.fail("Shouldn't have failed: %s" % ex)
+            
 if __name__ == '__main__':
     suite = unittest.TestSuite()
     loader = unittest.TestLoader()
-    for testcase in [WSManagerTest]:
+    for testcase in [WSManagerTest, WSSelectPollerTest]:
         tests = loader.loadTestsFromTestCase(testcase)
         suite.addTests(tests)
     unittest.TextTestRunner(verbosity=2).run(suite)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/test/test_utils.py 
new/ws4py-0.3.5/test/test_utils.py
--- old/ws4py-0.3.4/test/test_utils.py  1970-01-01 01:00:00.000000000 +0100
+++ new/ws4py-0.3.5/test/test_utils.py  2016-06-05 17:59:03.000000000 +0200
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+import unittest
+
+from ws4py import format_addresses
+from ws4py.websocket import WebSocket
+from mock import MagicMock
+
+class WSUtilities(unittest.TestCase):
+    def test_format_address(self):
+        m = MagicMock()
+        m.getsockname.return_value = ('127.0.0.1', 52300, None, None)
+        m.getpeername.return_value = ('127.0.0.1', 4800, None, None)
+        ws = WebSocket(sock=m)
+
+        log = format_addresses(ws)
+        self.assertEqual(log, "[Local => 127.0.0.1:52300 | Remote => 
127.0.0.1:4800]")
+        
+if __name__ == '__main__':
+    suite = unittest.TestSuite()
+    loader = unittest.TestLoader()
+    for testcase in [WSUtilities]:
+        tests = loader.loadTestsFromTestCase(testcase)
+        suite.addTests(tests)
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/test/test_websocket.py 
new/ws4py-0.3.5/test/test_websocket.py
--- old/ws4py-0.3.4/test/test_websocket.py      2014-01-25 22:30:07.000000000 
+0100
+++ new/ws4py-0.3.5/test/test_websocket.py      2016-06-05 17:59:03.000000000 
+0200
@@ -103,7 +103,7 @@
         self.assertFalse(ws.once())
         
     def test_no_bytes_were_read(self):
-        m = MagicMock()
+        m = MagicMock(spec=socket.socket)
         m.recv.return_value = b''
         ws = WebSocket(sock=m)
         self.assertFalse(ws.once())
@@ -169,6 +169,14 @@
             ws.process(b'unused for this test')
             c.assert_called_once_with(1000, b'test closing')
             
+    def test_sending_ping(self):
+        tm = PingControlMessage("hello").single(mask=False)
+        
+        m = MagicMock()
+        ws = WebSocket(sock=m)
+        ws.ping("hello")
+        m.sendall.assert_called_once_with(tm)
+
 
 if __name__ == '__main__':
     suite = unittest.TestSuite()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py/__init__.py 
new/ws4py-0.3.5/ws4py/__init__.py
--- old/ws4py-0.3.4/ws4py/__init__.py   2014-03-29 16:16:55.000000000 +0100
+++ new/ws4py-0.3.5/ws4py/__init__.py   2016-06-05 17:59:03.000000000 +0200
@@ -27,9 +27,10 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #
 import logging
+import logging.handlers as handlers
 
 __author__ = "Sylvain Hellegouarch"
-__version__ = "0.3.4"
+__version__ = "0.3.5"
 __all__ = ['WS_KEY', 'WS_VERSION', 'configure_logger', 'format_addresses']
 
 WS_KEY = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py/async_websocket.py 
new/ws4py-0.3.5/ws4py/async_websocket.py
--- old/ws4py-0.3.4/ws4py/async_websocket.py    2014-01-25 22:30:21.000000000 
+0100
+++ new/ws4py-0.3.5/ws4py/async_websocket.py    1970-01-01 01:00:00.000000000 
+0100
@@ -1,126 +0,0 @@
-# -*- 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py/client/__init__.py 
new/ws4py-0.3.5/ws4py/client/__init__.py
--- old/ws4py-0.3.4/ws4py/client/__init__.py    2014-01-25 22:30:21.000000000 
+0100
+++ new/ws4py-0.3.5/ws4py/client/__init__.py    2016-06-05 17:59:03.000000000 
+0200
@@ -205,7 +205,8 @@
         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)
@@ -245,11 +246,10 @@
         handshake.
         """
         headers = [
-            ('Host', self.host),
+            ('Host', '%s:%s' % (self.host, self.port)),
             ('Connection', 'Upgrade'),
             ('Upgrade', 'websocket'),
             ('Sec-WebSocket-Key', self.key.decode('utf-8')),
-            ('Origin', self.url),
             ('Sec-WebSocket-Version', str(max(WS_VERSION)))
             ]
         
@@ -259,6 +259,19 @@
         if self.extra_headers:
             headers.extend(self.extra_headers)
 
+        if not any(x for x in headers if x[0].lower() == 'origin'):
+
+            scheme, url = self.url.split(":", 1)
+            parsed = urlsplit(url, scheme="http")
+            if parsed.hostname:
+                self.host = parsed.hostname
+            else:
+                self.host = 'localhost'
+            origin = scheme + '://' + parsed.hostname
+            if parsed.port:
+                origin = origin + ':' + str(parsed.port)
+            headers.append(('Origin', origin))
+
         return headers
 
     @property
@@ -298,21 +311,24 @@
             header = header.strip().lower()
             value = value.strip().lower()
 
-            if header == 'upgrade' and value != 'websocket':
+            if header == b'upgrade' and value != b'websocket':
                 raise HandshakeError("Invalid Upgrade header: %s" % value)
 
-            elif header == 'connection' and value != 'upgrade':
+            elif header == b'connection' and value != b'upgrade':
                 raise HandshakeError("Invalid Connection header: %s" % value)
 
-            elif header == 'sec-websocket-accept':
-                match = b64encode(sha1(self.key.encode('utf-8') + 
WS_KEY).digest())
+            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 == 'sec-websocket-protocol':
+            elif header == b'sec-websocket-protocol':
                 protocols = ','.join(value)
 
-            elif header == 'sec-websocket-extensions':
+            elif header == b'sec-websocket-extensions':
                 extensions = ','.join(value)
 
         return protocols, extensions
+
+    def handshake_ok(self):
+        self.opened()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py/client/tornadoclient.py 
new/ws4py-0.3.5/ws4py/client/tornadoclient.py
--- old/ws4py-0.3.4/ws4py/client/tornadoclient.py       2014-03-29 
13:21:08.000000000 +0100
+++ new/ws4py-0.3.5/ws4py/client/tornadoclient.py       2016-06-05 
17:59:03.000000000 +0200
@@ -33,10 +33,10 @@
         """
         WebSocketBaseClient.__init__(self, url, protocols, extensions,
                                      ssl_options=ssl_options, headers=headers)
-        self.ssl_options["do_handshake_on_connect"] = False
         if self.scheme == "wss":
-            self.sock = ssl.wrap_socket(self.sock, **self.ssl_options)
-            self.io = iostream.SSLIOStream(self.sock, io_loop)
+            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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py/server/cherrypyserver.py 
new/ws4py-0.3.5/ws4py/server/cherrypyserver.py
--- old/ws4py-0.3.4/ws4py/server/cherrypyserver.py      2014-01-25 
22:30:21.000000000 +0100
+++ new/ws4py-0.3.5/ws4py/server/cherrypyserver.py      2016-06-05 
17:59:03.000000000 +0200
@@ -69,7 +69,7 @@
 import cherrypy
 from cherrypy import Tool
 from cherrypy.process import plugins
-from cherrypy.wsgiserver import HTTPConnection, HTTPRequest
+from cherrypy.wsgiserver import HTTPConnection, HTTPRequest, KnownLengthRFile
 
 from ws4py import WS_KEY, WS_VERSION
 from ws4py.exc import HandshakeError
@@ -197,7 +197,11 @@
             response.headers['Sec-WebSocket-Extensions'] = 
','.join(ws_extensions)
 
         addr = (request.remote.ip, request.remote.port)
-        ws_conn = get_connection(request.rfile.rfile)
+        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)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py/server/geventserver.py 
new/ws4py-0.3.5/ws4py/server/geventserver.py
--- old/ws4py-0.3.4/ws4py/server/geventserver.py        2014-01-25 
22:30:21.000000000 +0100
+++ new/ws4py-0.3.5/ws4py/server/geventserver.py        2016-06-05 
17:59:03.000000000 +0200
@@ -16,7 +16,6 @@
 
 """
 import logging
-import sys
 
 import gevent
 from gevent.pywsgi import WSGIHandler, WSGIServer as _WSGIServer
@@ -25,11 +24,13 @@
 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`
@@ -43,24 +44,25 @@
     def run_application(self):
         upgrade_header = self.environ.get('HTTP_UPGRADE', '').lower()
         if upgrade_header:
-            try:
-                # 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()
-            except:
-                raise
-            else:
-                del self.environ['ws4py.socket']
-                self.socket = None
-                self.rfile.close()
-
-                ws = self.environ.pop('ws4py.websocket')
-                if ws:
-                    self.server.pool.track(ws)
+            # 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.
@@ -85,7 +87,8 @@
                 pass
             finally:
                 self.discard(greenlet)
-                
+
+
 class WSGIServer(_WSGIServer):
     handler_class = WebSocketWSGIHandler
 
@@ -106,8 +109,8 @@
         self.pool.clear()
         _WSGIServer.stop(self, *args, **kwargs)
 
+
 if __name__ == '__main__':
-    import os
 
     from ws4py import configure_logger
     configure_logger()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py/server/tulipserver.py 
new/ws4py-0.3.5/ws4py/server/tulipserver.py
--- old/ws4py-0.3.4/ws4py/server/tulipserver.py 2014-01-25 22:30:21.000000000 
+0100
+++ new/ws4py-0.3.5/ws4py/server/tulipserver.py 1970-01-01 01:00:00.000000000 
+0100
@@ -1,220 +0,0 @@
-# -*- 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)
-
-        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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py/server/wsgirefserver.py 
new/ws4py-0.3.5/ws4py/server/wsgirefserver.py
--- old/ws4py-0.3.4/ws4py/server/wsgirefserver.py       2014-01-25 
22:30:21.000000000 +0100
+++ new/ws4py-0.3.5/ws4py/server/wsgirefserver.py       2016-06-05 
17:59:03.000000000 +0200
@@ -52,6 +52,7 @@
         """
         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):
         """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py/websocket.py 
new/ws4py-0.3.5/ws4py/websocket.py
--- old/ws4py-0.3.4/ws4py/websocket.py  2014-01-25 22:30:21.000000000 +0100
+++ new/ws4py-0.3.5/ws4py/websocket.py  2016-06-05 17:59:03.000000000 +0200
@@ -1,14 +1,22 @@
 # -*- coding: utf-8 -*-
 import logging
 import socket
+import ssl
 import time
 import threading
 import types
 
+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, PongControlMessage
+from ws4py.messaging import Message, PingControlMessage,\
+    PongControlMessage
 from ws4py.compat import basestring, unicode
 
 DEFAULT_READING_SIZE = 2
@@ -98,7 +106,12 @@
         """
         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.
@@ -210,6 +223,13 @@
             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,
@@ -229,6 +249,22 @@
         """
         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
@@ -277,6 +313,50 @@
         else:
             raise ValueError("Unsupported type '%s' passed to send()" % 
type(payload))
 
+    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
@@ -298,8 +378,11 @@
 
         try:
             b = self.sock.recv(self.reading_buffer_size)
-        except socket.error:
-            logger.exception("Failed to receive data")
+            # This will only make sense with secure sockets.
+            if self._is_secure:
+                b += self._get_from_pending()
+        except (socket.error, OSError, pyOpenSSLError) as e:
+            self.unhandled_error(e)
             return False
         else:
             if not self.process(b):
@@ -323,7 +406,7 @@
         self.client_terminated = self.server_terminated = True
 
         try:
-            if not s.closing:
+            if s.closing is None:
                 self.closed(1006, "Going away")
             else:
                 self.closed(s.closing.code, s.closing.reason)
@@ -362,7 +445,6 @@
                 self.close(s.closing.code, s.closing.reason)
             else:
                 self.client_terminated = True
-            s = None
             return False
 
         if s.errors:
@@ -370,7 +452,6 @@
                 logger.debug("Error message received (%d) '%s'" % (error.code, 
error.reason))
                 self.close(error.code, error.reason)
             s.errors = []
-            s = None
             return False
 
         if s.has_message:
@@ -378,7 +459,6 @@
             if s.message is not None:
                 s.message.data = None
                 s.message = None
-            s = None
             return True
 
         if s.pings:
@@ -391,7 +471,6 @@
                 self.ponged(pong)
             s.pongs = []
 
-        s = None
         return True
 
     def run(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py.egg-info/PKG-INFO 
new/ws4py-0.3.5/ws4py.egg-info/PKG-INFO
--- old/ws4py-0.3.4/ws4py.egg-info/PKG-INFO     2014-03-30 12:28:57.000000000 
+0200
+++ new/ws4py-0.3.5/ws4py.egg-info/PKG-INFO     2016-06-05 21:28:29.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: ws4py
-Version: 0.3.4
+Version: 0.3.5
 Summary: WebSocket client and server library for Python 2 and 3 as well as PyPy
 Home-page: https://github.com/Lawouach/WebSocket-for-Python
 Author: Sylvain Hellegouarch
@@ -20,6 +20,7 @@
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Communications
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ws4py-0.3.4/ws4py.egg-info/SOURCES.txt 
new/ws4py-0.3.5/ws4py.egg-info/SOURCES.txt
--- old/ws4py-0.3.4/ws4py.egg-info/SOURCES.txt  2014-03-30 12:28:57.000000000 
+0200
+++ new/ws4py-0.3.5/ws4py.egg-info/SOURCES.txt  2016-06-05 21:28:30.000000000 
+0200
@@ -2,12 +2,13 @@
 test/test_cherrypy.py
 test/test_client.py
 test/test_frame.py
+test/test_logger.py
 test/test_manager.py
 test/test_messaging.py
 test/test_stream.py
+test/test_utils.py
 test/test_websocket.py
 ws4py/__init__.py
-ws4py/async_websocket.py
 ws4py/compat.py
 ws4py/exc.py
 ws4py/framing.py
@@ -27,6 +28,5 @@
 ws4py/server/__init__.py
 ws4py/server/cherrypyserver.py
 ws4py/server/geventserver.py
-ws4py/server/tulipserver.py
 ws4py/server/wsgirefserver.py
 ws4py/server/wsgiutils.py
\ No newline at end of file


Reply via email to